1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Changed the way effects are rendered to improve performance

This commit is contained in:
Darth Affe 2016-06-13 21:22:59 +02:00
parent e438340705
commit 436eb365a1
10 changed files with 106 additions and 148 deletions

View File

@ -99,6 +99,7 @@ namespace Artemis.DeviceProviders.Corsair
// For STRAFE, stretch the image on row 2.
if (_keyboard.DeviceInfo.Model == "STRAFE RGB")
{
//TODO DarthAffe 13.06.2016: This might leak memory since it's not disposed, but I don't want to change it without the ability to test if it still works
var strafeBitmap = new Bitmap(22, 8);
using (var g = Graphics.FromImage(strafeBitmap))
{

View File

@ -16,6 +16,7 @@ namespace Artemis.Managers
private readonly EffectManager _effectManager;
private readonly ILogger _logger;
private readonly Timer _loopTimer;
private Bitmap _keyboardBitmap;
public LoopManager(ILogger logger, EffectManager effectManager, DeviceManager deviceManager)
{
@ -38,6 +39,7 @@ namespace Artemis.Managers
{
_loopTimer.Stop();
_loopTimer.Dispose();
_keyboardBitmap?.Dispose();
}
public void Start()
@ -67,6 +69,9 @@ namespace Artemis.Managers
_effectManager.ChangeEffect(lastEffect);
}
// I assume that it's safe to use ActiveKeyboard and ActifeEffect here since both is checked above
_keyboardBitmap = _deviceManager.ActiveKeyboard.KeyboardBitmap(_effectManager.ActiveEffect.KeyboardScale);
Running = true;
}
@ -79,6 +84,8 @@ namespace Artemis.Managers
Running = false;
_deviceManager.ReleaseActiveKeyboard();
_keyboardBitmap?.Dispose();
_keyboardBitmap = null;
}
private void Render(object sender, ElapsedEventArgs e)
@ -117,47 +124,37 @@ namespace Artemis.Managers
renderEffect.Update();
// Get ActiveEffect's bitmap
Bitmap bitmap = null;
Brush mouseBrush = null;
Brush headsetBrush = null;
var mice = _deviceManager.MiceProviders.Where(m => m.CanUse).ToList();
var headsets = _deviceManager.HeadsetProviders.Where(m => m.CanUse).ToList();
if (renderEffect.Initialized)
renderEffect.Render(out bitmap, out mouseBrush, out headsetBrush, mice.Any(), headsets.Any());
// Draw enabled overlays on top of the renderEffect
foreach (var overlayModel in _effectManager.EnabledOverlays)
using (Graphics keyboardGraphics = Graphics.FromImage(_keyboardBitmap))
{
overlayModel.Update();
overlayModel.RenderOverlay(ref bitmap, ref mouseBrush, ref headsetBrush, mice.Any(), headsets.Any());
}
// Fill the bitmap's background with black to avoid trailing colors on some keyboards
keyboardGraphics.Clear(Color.Black);
// Update mice and headsets
foreach (var mouse in mice)
mouse.UpdateDevice(mouseBrush);
foreach (var headset in headsets)
headset.UpdateDevice(headsetBrush);
if (renderEffect.Initialized)
renderEffect.Render(keyboardGraphics, out mouseBrush, out headsetBrush, mice.Any(),
headsets.Any());
// If no bitmap was generated this frame is done
if (bitmap == null)
return;
// Fill the bitmap's background with black to avoid trailing colors on some keyboards
// Bitmaps needs to be disposd!
using (var fixedBmp = new Bitmap(bitmap.Width, bitmap.Height))
{
using (var g = Graphics.FromImage(fixedBmp))
// Draw enabled overlays on top of the renderEffect
foreach (var overlayModel in _effectManager.EnabledOverlays)
{
g.Clear(Color.Black);
g.DrawImage(bitmap, 0, 0);
overlayModel.Update();
overlayModel.RenderOverlay(keyboardGraphics, ref mouseBrush, ref headsetBrush, mice.Any(),
headsets.Any());
}
bitmap = fixedBmp;
// Update the keyboard
_deviceManager.ActiveKeyboard?.DrawBitmap(bitmap);
// Update mice and headsets
foreach (var mouse in mice)
mouse.UpdateDevice(mouseBrush);
foreach (var headset in headsets)
headset.UpdateDevice(headsetBrush);
}
// Update the keyboard
_deviceManager.ActiveKeyboard?.DrawBitmap(_keyboardBitmap);
}
}
}

View File

@ -13,9 +13,10 @@ namespace Artemis.Models
{
public delegate void SettingsUpdateHandler(EffectSettings settings);
public bool Initialized;
public MainManager MainManager;
public string Name;
public bool Initialized { get; set; }
public MainManager MainManager { get; set; }
public string Name { get; set; }
public int KeyboardScale { get; set; } = 4;
protected EffectModel(MainManager mainManager, IDataModel dataModel)
{
@ -36,10 +37,8 @@ namespace Artemis.Models
public abstract void Update();
// Called after every update
public virtual void Render(out Bitmap keyboard, out Brush mouse, out Brush headset, bool renderMice,
bool renderHeadsets)
public virtual void Render(Graphics keyboard, out Brush mouse, out Brush headset, bool renderMice, bool renderHeadsets)
{
keyboard = null;
mouse = null;
headset = null;
@ -50,8 +49,7 @@ namespace Artemis.Models
var renderLayers = GetRenderLayers(renderMice, renderHeadsets);
// Render the keyboard layer-by-layer
keyboard = Profile.GenerateBitmap(renderLayers, DataModel,
MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(4), false, true);
Profile.DrawProfile(keyboard, renderLayers, DataModel, MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(KeyboardScale), false, true);
// Render the first enabled mouse (will default to null if renderMice was false)
mouse = Profile.GenerateBrush(renderLayers.LastOrDefault(l => l.LayerType == LayerType.Mouse), DataModel);
// Render the first enabled headset (will default to null if renderHeadsets was false)

View File

@ -29,7 +29,7 @@ namespace Artemis.Models
}
}
public abstract void RenderOverlay(ref Bitmap keyboard, ref Brush mouse, ref Brush headset, bool renderMice,
public abstract void RenderOverlay(Graphics keyboard, ref Brush mouse, ref Brush headset, bool renderMice,
bool renderHeadsets);
}
}

View File

@ -45,7 +45,7 @@ namespace Artemis.Models.Profiles
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((ProfileModel) obj);
return Equals((ProfileModel)obj);
}
public override int GetHashCode()
@ -53,8 +53,8 @@ namespace Artemis.Models.Profiles
unchecked
{
var hashCode = Name?.GetHashCode() ?? 0;
hashCode = (hashCode*397) ^ (KeyboardSlug?.GetHashCode() ?? 0);
hashCode = (hashCode*397) ^ (GameName?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (KeyboardSlug?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ (GameName?.GetHashCode() ?? 0);
return hashCode;
}
}
@ -66,7 +66,7 @@ namespace Artemis.Models.Profiles
Layers[i].Order = i;
}
public Bitmap GenerateBitmap<T>(Rect keyboardRect, IDataModel dataModel, bool preview,
public void DrawProfile<T>(Graphics keyboard, Rect keyboardRect, IDataModel dataModel, bool preview,
bool updateAnimations)
{
var visual = new DrawingVisual();
@ -84,7 +84,10 @@ namespace Artemis.Models.Profiles
c.Pop();
}
return ImageUtilities.DrawinVisualToBitmap(visual, keyboardRect);
//TODO DarthAffe 13.06.2016: This is just to make it work
//It would be better to somehow draw directly but I don't really understand dat visual stuff right now :p
using (Bitmap bmp = ImageUtilities.DrawinVisualToBitmap(visual, keyboardRect))
keyboard.DrawImage(bmp, new PointF(0, 0));
}
public Brush GenerateBrush<T>(IDataModel dataModel, LayerType type, bool preview, bool updateAnimations)
@ -161,7 +164,7 @@ namespace Artemis.Models.Profiles
if (layer.LayerType != LayerType.Keyboard && layer.LayerType != LayerType.KeyboardGif)
continue;
var props = (KeyboardPropertiesModel) layer.Properties;
var props = (KeyboardPropertiesModel)layer.Properties;
var layerRect = new Rect(new Point(props.X, props.Y), new Size(props.Width, props.Height));
if (keyboardRectangle.Contains(layerRect))
continue;
@ -173,15 +176,15 @@ namespace Artemis.Models.Profiles
}
/// <summary>
/// Generates a bitmap showing all the provided layers of type Keyboard and KeyboardGif
/// Draw all the provided layers of type Keyboard and KeyboardGif
/// </summary>
/// <param name="keyboard">The graphics to draw on</param>
/// <param name="renderLayers">The layers to render</param>
/// <param name="dataModel">The data model to base the layer's properties on</param>
/// <param name="keyboardRect">A rectangle matching the current keyboard's size on a scale of 4, used for clipping</param>
/// <param name="preview">Indicates wheter the layer is drawn as a preview, ignoring dynamic properties</param>
/// <param name="updateAnimations">Wheter or not to update the layer's animations</param>
/// <returns>The generated bitmap</returns>
internal Bitmap GenerateBitmap(List<LayerModel> renderLayers, IDataModel dataModel, Rect keyboardRect,
internal void DrawProfile(Graphics keyboard, List<LayerModel> renderLayers, IDataModel dataModel, Rect keyboardRect,
bool preview,
bool updateAnimations)
{
@ -204,7 +207,10 @@ namespace Artemis.Models.Profiles
c.Pop();
}
return ImageUtilities.DrawinVisualToBitmap(visual, keyboardRect);
//TODO DarthAffe 13.06.2016: This is just to make it work
//It would be better to somehow draw directly but I don't really understand dat visual stuff right now :p
using (Bitmap bmp = ImageUtilities.DrawinVisualToBitmap(visual, keyboardRect))
keyboard.DrawImage(bmp, new PointF(0, 0));
}
/// <summary>

View File

@ -30,14 +30,11 @@ namespace Artemis.Modules.Effects.AudioVisualizer
Name = "Audiovisualizer";
DeviceIds = new List<string>();
SpectrumData = new List<byte>();
Scale = 4;
Initialized = false;
}
public int Lines { get; set; }
public int Scale { get; set; }
public AudioVisualizerSettings Settings { get; set; }
public List<byte> SpectrumData { get; set; }
public List<KeyboardRectangle> SoundRectangles { get; set; }
@ -80,7 +77,8 @@ namespace Artemis.Modules.Effects.AudioVisualizer
ColorHelpers.ToDrawingColor(Settings.MiddleColor),
ColorHelpers.ToDrawingColor(Settings.BottomColor)
},
LinearGradientMode.Vertical) {ContainedBrush = false, Height = 0});
LinearGradientMode.Vertical)
{ ContainedBrush = false, Height = 0 });
}
_sensitivity = Settings.Sensitivity;
_fromBottom = Settings.FromBottom;
@ -120,22 +118,22 @@ namespace Artemis.Modules.Effects.AudioVisualizer
if (SpectrumData.Count - 1 < i || SpectrumData[i] == 0)
height = 0;
else
height = (int) Math.Round(SpectrumData[i]/2.55);
height = (int)Math.Round(SpectrumData[i] / 2.55);
// Apply Sensitivity setting
height = height*_sensitivity;
height = height * _sensitivity;
var keyboardHeight =
(int) Math.Round(MainManager.DeviceManager.ActiveKeyboard.Height/100.00*height*Scale);
(int)Math.Round(MainManager.DeviceManager.ActiveKeyboard.Height / 100.00 * height * KeyboardScale);
if (keyboardHeight > SoundRectangles[i].Height)
SoundRectangles[i].Height = keyboardHeight;
else
SoundRectangles[i].Height = SoundRectangles[i].Height - Settings.FadeSpeed;
// Apply Bars setting
SoundRectangles[i].X = i*Scale;
SoundRectangles[i].Width = Scale;
SoundRectangles[i].X = i * KeyboardScale;
SoundRectangles[i].Width = KeyboardScale;
if (_fromBottom)
SoundRectangles[i].Y = MainManager.DeviceManager.ActiveKeyboard.Height*Scale -
SoundRectangles[i].Y = MainManager.DeviceManager.ActiveKeyboard.Height * KeyboardScale -
SoundRectangles[i].Height;
}
_generating = false;
@ -166,7 +164,7 @@ namespace Artemis.Modules.Effects.AudioVisualizer
for (x = 0; x < Lines; x++)
{
float peak = 0;
var b1 = (int) Math.Pow(2, x*10.0/(Lines - 1));
var b1 = (int)Math.Pow(2, x * 10.0 / (Lines - 1));
if (b1 > 2047)
b1 = 2047;
if (b1 <= b0)
@ -176,12 +174,12 @@ namespace Artemis.Modules.Effects.AudioVisualizer
if (peak < e.Result[1 + b0].X)
peak = e.Result[1 + b0].X;
}
var y = (int) (Math.Sqrt(peak)*3*255 - 4);
var y = (int)(Math.Sqrt(peak) * 3 * 255 - 4);
if (y > 255)
y = 255;
if (y < 0)
y = 0;
SpectrumData.Add((byte) y);
SpectrumData.Add((byte)y);
}
}
@ -190,10 +188,9 @@ namespace Artemis.Modules.Effects.AudioVisualizer
return null;
}
public override void Render(out Bitmap keyboard, out Brush mouse, out Brush headset, bool renderMice,
public override void Render(Graphics keyboard, out Brush mouse, out Brush headset, bool renderMice,
bool renderHeadsets)
{
keyboard = null;
mouse = null;
headset = null;
@ -203,12 +200,8 @@ namespace Artemis.Modules.Effects.AudioVisualizer
// Lock the _spectrumData array while busy with it
_generating = true;
keyboard = MainManager.DeviceManager.ActiveKeyboard.KeyboardBitmap(Scale);
using (var g = Graphics.FromImage(keyboard))
{
foreach (var soundRectangle in SoundRectangles)
soundRectangle.Draw(g);
}
foreach (var soundRectangle in SoundRectangles)
soundRectangle.Draw(keyboard);
_generating = false;
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using Artemis.Managers;
using Artemis.Models;
@ -17,10 +16,7 @@ namespace Artemis.Modules.Effects.Bubbles
private static readonly Random _random = new Random();
private const int SCALE = 25;
private readonly List<Bubble> _bubbles = new List<Bubble>();
private Bitmap _bitmap;
public BubblesSettings Settings { get; }
@ -32,6 +28,7 @@ namespace Artemis.Modules.Effects.Bubbles
: base(mainManager, null)
{
Name = "Bubbles";
KeyboardScale = 25;
Settings = settings;
Initialized = false;
}
@ -42,8 +39,7 @@ namespace Artemis.Modules.Effects.Bubbles
public override void Enable()
{
Rect rect = MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(SCALE);
_bitmap = MainManager.DeviceManager.ActiveKeyboard.KeyboardBitmap(SCALE);
Rect rect = MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(KeyboardScale);
for (int i = 0; i < Settings.BubbleCount; i++)
{
@ -62,14 +58,13 @@ namespace Artemis.Modules.Effects.Bubbles
public override void Dispose()
{
_bitmap?.Dispose();
_bubbles.Clear();
Initialized = false;
}
public override void Update()
{
Rect keyboardRectangle = MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(SCALE);
Rect keyboardRectangle = MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(KeyboardScale);
foreach (Bubble bubble in _bubbles)
{
if (Settings.IsShiftColors)
@ -80,20 +75,13 @@ namespace Artemis.Modules.Effects.Bubbles
}
}
public override void Render(out Bitmap keyboard, out Brush mouse, out Brush headset, bool renderMice, bool renderHeadsets)
public override void Render(Graphics keyboard, out Brush mouse, out Brush headset, bool renderMice, bool renderHeadsets)
{
keyboard = _bitmap;
mouse = null;
headset = null;
using (Graphics g = Graphics.FromImage(keyboard))
{
g.Clear(Color.Transparent);
g.SmoothingMode = SmoothingMode.None;
foreach (Bubble bubble in _bubbles)
bubble.Draw(g);
}
foreach (Bubble bubble in _bubbles)
bubble.Draw(keyboard);
}
public override List<LayerModel> GetRenderLayers(bool renderMice, bool renderHeadsets)

View File

@ -35,10 +35,9 @@ namespace Artemis.Modules.Effects.ProfilePreview
return Profile.GetRenderLayers<ProfilePreviewDataModel>(DataModel, renderMice, renderHeadsets, true);
}
public override void Render(out Bitmap keyboard, out Brush mouse, out Brush headset, bool renderMice,
public override void Render(Graphics keyboard, out Brush mouse, out Brush headset, bool renderMice,
bool renderHeadsets)
{
keyboard = null;
mouse = null;
headset = null;
@ -49,8 +48,7 @@ namespace Artemis.Modules.Effects.ProfilePreview
var renderLayers = GetRenderLayers(renderMice, renderHeadsets);
// Render the keyboard layer-by-layer
keyboard = Profile?.GenerateBitmap(renderLayers, DataModel,
MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(4), true, true);
Profile?.DrawProfile(keyboard, renderLayers, DataModel, MainManager.DeviceManager.ActiveKeyboard.KeyboardRectangle(KeyboardScale), true, true);
// Render the first enabled mouse (will default to null if renderMice was false)
mouse = Profile?.GenerateBrush(renderLayers.LastOrDefault(l => l.LayerType == LayerType.Mouse), DataModel);
// Render the first enabled headset (will default to null if renderHeadsets was false)

View File

@ -25,11 +25,8 @@ namespace Artemis.Modules.Effects.TypeWave
_randomColor = Color.Red;
Settings = settings;
Initialized = false;
Scale = 4;
}
public int Scale { get; set; }
public TypeWaveSettings Settings { get; set; }
public override void Dispose()
@ -49,8 +46,8 @@ namespace Artemis.Modules.Effects.TypeWave
return;
_waves.Add(Settings.IsRandomColors
? new Wave(new Point(keyMatch.PosX*Scale, keyMatch.PosY*Scale), 0, _randomColor)
: new Wave(new Point(keyMatch.PosX*Scale, keyMatch.PosY*Scale), 0,
? new Wave(new Point(keyMatch.PosX * KeyboardScale, keyMatch.PosY * KeyboardScale), 0, _randomColor)
: new Wave(new Point(keyMatch.PosX * KeyboardScale, keyMatch.PosY * KeyboardScale), 0,
ColorHelpers.ToDrawingColor(Settings.WaveColor)));
}
@ -74,12 +71,12 @@ namespace Artemis.Modules.Effects.TypeWave
// TODO: Get from settings
var fps = 25;
_waves[i].Size += Settings.SpreadSpeed*Scale;
_waves[i].Size += Settings.SpreadSpeed * KeyboardScale;
if (Settings.IsShiftColors)
_waves[i].Color = ColorHelpers.ShiftColor(_waves[i].Color, Settings.ShiftColorSpeed);
var decreaseAmount = 255/(Settings.TimeToLive/fps);
var decreaseAmount = 255 / (Settings.TimeToLive / fps);
_waves[i].Color = Color.FromArgb(
_waves[i].Color.A - decreaseAmount, _waves[i].Color.R,
_waves[i].Color.G,
@ -98,51 +95,43 @@ namespace Artemis.Modules.Effects.TypeWave
return null;
}
public override void Render(out Bitmap keyboard, out Brush mouse, out Brush headset, bool renderMice,
public override void Render(Graphics keyboard, out Brush mouse, out Brush headset, bool renderMice,
bool renderHeadsets)
{
keyboard = null;
mouse = null;
headset = null;
if (_waves.Count == 0)
return;
keyboard = MainManager.DeviceManager.ActiveKeyboard.KeyboardBitmap(Scale);
using (var g = Graphics.FromImage(keyboard))
// Don't want a for-each, collection is changed in different thread
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < _waves.Count; i++)
{
g.Clear(Color.Transparent);
g.SmoothingMode = SmoothingMode.HighQuality;
if (_waves[i].Size == 0)
continue;
var path = new GraphicsPath();
path.AddEllipse(_waves[i].Point.X - _waves[i].Size / 2, _waves[i].Point.Y - _waves[i].Size / 2,
_waves[i].Size, _waves[i].Size);
// Don't want a for-each, collection is changed in different thread
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < _waves.Count; i++)
Color fillColor;
if (MainManager.DeviceManager.ActiveKeyboard is CorsairRGB)
fillColor = Color.Black;
else
fillColor = Color.Transparent;
var pthGrBrush = new PathGradientBrush(path)
{
if (_waves[i].Size == 0)
continue;
var path = new GraphicsPath();
path.AddEllipse(_waves[i].Point.X - _waves[i].Size/2, _waves[i].Point.Y - _waves[i].Size/2,
_waves[i].Size, _waves[i].Size);
SurroundColors = new[] { _waves[i].Color },
CenterColor = fillColor
};
Color fillColor;
if (MainManager.DeviceManager.ActiveKeyboard is CorsairRGB)
fillColor = Color.Black;
else
fillColor = Color.Transparent;
keyboard.FillPath(pthGrBrush, path);
pthGrBrush.FocusScales = new PointF(0.3f, 0.8f);
var pthGrBrush = new PathGradientBrush(path)
{
SurroundColors = new[] {_waves[i].Color},
CenterColor = fillColor
};
g.FillPath(pthGrBrush, path);
pthGrBrush.FocusScales = new PointF(0.3f, 0.8f);
g.FillPath(pthGrBrush, path);
g.DrawEllipse(new Pen(pthGrBrush, 1), _waves[i].Point.X - _waves[i].Size/2,
_waves[i].Point.Y - _waves[i].Size/2, _waves[i].Size, _waves[i].Size);
}
keyboard.FillPath(pthGrBrush, path);
keyboard.DrawEllipse(new Pen(pthGrBrush, 1), _waves[i].Point.X - _waves[i].Size / 2,
_waves[i].Point.Y - _waves[i].Size / 2, _waves[i].Size, _waves[i].Size);
}
}
}

View File

@ -46,10 +46,10 @@ namespace Artemis.Modules.Overlays.VolumeDisplay
if (VolumeDisplay.Ttl < 1)
return;
var decreaseAmount = 500/fps;
var decreaseAmount = 500 / fps;
VolumeDisplay.Ttl = VolumeDisplay.Ttl - decreaseAmount;
if (VolumeDisplay.Ttl < 128)
VolumeDisplay.Transparancy = (byte) (VolumeDisplay.Transparancy - 20);
VolumeDisplay.Transparancy = (byte)(VolumeDisplay.Transparancy - 20);
try
{
@ -57,26 +57,13 @@ namespace Artemis.Modules.Overlays.VolumeDisplay
var volumeFloat =
enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console)
.AudioEndpointVolume.MasterVolumeLevelScalar;
VolumeDisplay.Volume = (int) (volumeFloat*100);
VolumeDisplay.Volume = (int)(volumeFloat * 100);
}
catch (COMException)
{
}
}
public Bitmap GenerateBitmap(Bitmap bitmap)
{
if (VolumeDisplay == null)
return bitmap;
if (VolumeDisplay.Ttl < 1)
return bitmap;
using (var g = Graphics.FromImage(bitmap))
VolumeDisplay.Draw(g);
return bitmap;
}
public override List<LayerModel> GetRenderLayers(bool renderMice, bool renderHeadsets)
{
return null;
@ -91,10 +78,11 @@ namespace Artemis.Modules.Overlays.VolumeDisplay
VolumeDisplay.Transparancy = 255;
}
public override void RenderOverlay(ref Bitmap keyboard, ref Brush mouse, ref Brush headset, bool renderMice,
public override void RenderOverlay(Graphics keyboard, ref Brush mouse, ref Brush headset, bool renderMice,
bool renderHeadsets)
{
keyboard = GenerateBitmap(keyboard ?? MainManager.DeviceManager.ActiveKeyboard.KeyboardBitmap(4));
if (VolumeDisplay != null && VolumeDisplay.Ttl >= 1)
VolumeDisplay.Draw(keyboard);
}
}
}