From e1357f1bf02f0763ae86fc1f33afd599ce00dad7 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sat, 17 Oct 2026 20:34:25 +0200 Subject: [PATCH] Core - Keep small LEDs addressable at reduced render scales Sample each LED from its unsnapped absolute boundary instead of the render-grid-aligned AbsoluteRectangle, ensuring at least one pixel and clamping to the texture. Also apply the saved render scale at startup. --- src/Artemis.Core/RGB.NET/SKTexture.cs | 29 ++++++++++++++++------ src/Artemis.Core/Services/RenderService.cs | 8 +++++- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs index 14aae47e2..c117b9b11 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -29,19 +29,32 @@ public sealed class SKTexture : ITexture, IDisposable foreach (ArtemisDevice artemisDevice in devices) { foreach (ArtemisLed artemisLed in artemisDevice.Leds) - { - _ledRects[artemisLed.RgbLed] = SKRectI.Create( - (int)(artemisLed.AbsoluteRectangle.Left * RenderScale), - (int)(artemisLed.AbsoluteRectangle.Top * RenderScale), - (int)(artemisLed.AbsoluteRectangle.Width * RenderScale), - (int)(artemisLed.AbsoluteRectangle.Height * RenderScale) - ); - } + _ledRects[artemisLed.RgbLed] = CreateSampleRect(artemisLed.RgbLed.AbsoluteBoundary, scale, width, height); } } #endregion + private static SKRectI CreateSampleRect(Rectangle boundary, float scale, int width, int height) + { + (int left, int right) = ScaleSpan(boundary.Location.X, boundary.Size.Width, scale, width); + (int top, int bottom) = ScaleSpan(boundary.Location.Y, boundary.Size.Height, scale, height); + return new SKRectI(left, top, right, bottom); + } + + private static (int Start, int End) ScaleSpan(float location, float size, float scale, int max) + { + float start = location * scale; + float end = (location + size) * scale; + if (end <= 0 || start >= max) + return (0, 0); + + // Always sample at least one pixel + int startPixel = Math.Clamp(start.RoundToInt(), 0, max - 1); + int endPixel = Math.Clamp(end.RoundToInt(), startPixel + 1, max); + return (startPixel, endPixel); + } + internal Color GetColorAtRenderTarget(in RenderTarget renderTarget) { if (_image == null) return Color.Transparent; diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs index bf2138dcb..3d28859b7 100644 --- a/src/Artemis.Core/Services/RenderService.cs +++ b/src/Artemis.Core/Services/RenderService.cs @@ -47,6 +47,7 @@ internal class RenderService : IRenderService, IRenderer, IDisposable _preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged; Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; + ApplyRenderScaleMultiplier(); _surfaceManager = new SurfaceManager(this, GraphicsContext, _targetFrameRateSetting.Value, (float) _renderScaleSetting.Value); } @@ -189,9 +190,14 @@ internal class RenderService : IRenderService, IRenderer, IDisposable _surfaceManager.UpdateTargetFrameRate(_targetFrameRateSetting.Value); } - private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) + private void ApplyRenderScaleMultiplier() { RenderScale.SetRenderScaleMultiplier((int) (1 / _renderScaleSetting.Value)); + } + + private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e) + { + ApplyRenderScaleMultiplier(); _surfaceManager.UpdateRenderScale((float) _renderScaleSetting.Value); }