mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
perf(spectrum): full-raw CPU renderer + dword blenders (frame 8.3ms -> 1.7ms)
Canvas на битмапе кадра больше не используется вообще (кроме редкого ADC-overload пути с восстановлением альфы) — убраны все raw<->canvas переходы, каждый из которых синкал весь битмап на Qt6 (~2мс): - RawBegin/RawEnd: прямой доступ к пикселям через RawImage.Data + BytesPerLine (bottom-up DIB win32 учтён отрицательным шагом) - RawVLine/RawHLine (с пунктиром), RawFillRect, RawTriangleDown, RawCurve (кривая = связные вертикальные сегменты по колонкам) - кэш текстовых масок (TRawTextMask, LRU 48): строка рендерится canvas'ом один раз белым-на-чёрном в свой мини-битмап, блитится raw с цветом; все тексты кадра теперь Courier New - конвертированы: маркер (метка без случайной цветной плашки), бикон- маркеры, буквы слайсов, слайс-линии, AGC (пунктир 4/2 и 1/2, подпись с фоном SpecFilter как раньше), края/VFO/TX-курсоры с треугольниками - пофреймовый альфа-OR проход удалён: альфа чинится один раз в кэше СЕТКИ после ребилда (canvas-текст там), кадр наследует её через memcpy Дворд-блендеры (два 16-бит лейна на умножение, /256 вместо /255, расхождение <0.4%) вместо побайтовых с div 255: - BandPlanOverlay.CompositeStrip (~1мс -> ~0.2мс) - SampleRateOverlay.CompositeCache (per-px альфа) - VfoOverlay.BlendBitmapKey (keyed-магента дворд-сравнением) - SpectrumView.DrawSpectrumGradient (source-лейны 1 раз на строку, ~1.6мс -> ~0.8мс) Порог перестройки бэндплана: полпикселя вместо 1 Гц (на QO-100 с decoder-lock центр подтюнивается непрерывно — был canvas-ребилд полоски каждый кадр). Композиты внутри raw-лока (их BeginUpdate вложенные). PerfLog: зона ui.composites отделена от ui.spec_alpha (=RawEnd). Замер на эфире (Pluto, 576k, 52fps): кадр 8.3мс -> 1.67мс (5x), со слайсом 2.1-2.3мс; UI-поток ~45% -> ~26% ядра, из них 15% — блиты paint_spectrum/paint_waterfall (потолок Qt6-вывода). Визуально проверено. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+29
-19
@@ -231,11 +231,18 @@ begin
|
||||
end;
|
||||
|
||||
function TBandPlanOverlay.NeedsRebuild(W: Integer): Boolean;
|
||||
var HalfPxHz: Double;
|
||||
begin
|
||||
// Порог перестройки — ПОЛПИКСЕЛЯ, а не 1 Гц: суб-пиксельный сдвиг центра
|
||||
// не меняет картинку, а на QO-100 с decoder-lock центр подтюнивается
|
||||
// непрерывно — порог 1 Гц заставлял полный canvas-ребилд полоски (и
|
||||
// canvas<->raw флип её кэша) на каждом кадре (~1.2 мс/кадр впустую).
|
||||
HalfPxHz := 0.5 * FSpanHz / Max(W, 1);
|
||||
if HalfPxHz < 1.0 then HalfPxHz := 1.0;
|
||||
Result := FCacheDirty or (FCacheW <> W)
|
||||
or (FKeyW <> W) or (FKeyEnabled <> FEnabled)
|
||||
or (Abs(FKeyCenter - FCenterFreq) >= 1.0)
|
||||
or (Abs(FKeySpan - FSpanHz) >= 1.0);
|
||||
or (Abs(FKeyCenter - FCenterFreq) >= HalfPxHz)
|
||||
or (Abs(FKeySpan - FSpanHz) >= HalfPxHz);
|
||||
end;
|
||||
|
||||
procedure TBandPlanOverlay.RebuildCache(W: Integer);
|
||||
@@ -252,15 +259,18 @@ end;
|
||||
|
||||
procedure TBandPlanOverlay.CompositeStrip(Target: TBitmap; DstY: Integer);
|
||||
// Альфа-композит FCache поверх Target в (0, DstY) с постоянной BANDPLAN_ALPHA.
|
||||
// Один проход по W × STRIP_H пикселей за кадр.
|
||||
// Один проход по W × STRIP_H пикселей за кадр. Дворд-блендинг: два 16-битных
|
||||
// лейна на умножение, деление /256 вместо /255 (расхождение <0.4%, глазом не
|
||||
// видно) — побайтовый вариант с div 255 стоил ~1мс/кадр на полной ширине.
|
||||
var
|
||||
X, Y, ty, InvA: Integer;
|
||||
RS, RD: PByte;
|
||||
const
|
||||
A = BANDPLAN_ALPHA;
|
||||
X, Y, ty, N: Integer;
|
||||
A256, I256, S, D: LongWord;
|
||||
RS, RD: PLongWord;
|
||||
begin
|
||||
if (Target = nil) or (FCacheW <= 0) then Exit;
|
||||
InvA := 255 - A;
|
||||
A256 := BANDPLAN_ALPHA + (BANDPLAN_ALPHA shr 7); // 0..256
|
||||
I256 := 256 - A256;
|
||||
N := Min(FCacheW, Target.Width);
|
||||
Target.BeginUpdate(False);
|
||||
FCache.BeginUpdate(False);
|
||||
try
|
||||
@@ -268,22 +278,22 @@ begin
|
||||
begin
|
||||
ty := DstY + Y;
|
||||
if (ty < 0) or (ty >= Target.Height) then Continue;
|
||||
RS := PByte(FCache.ScanLine[Y]);
|
||||
RD := PByte(Target.ScanLine[ty]);
|
||||
RS := PLongWord(FCache.ScanLine[Y]);
|
||||
RD := PLongWord(Target.ScanLine[ty]);
|
||||
if (RS = nil) or (RD = nil) then Continue;
|
||||
for X := 0 to FCacheW - 1 do
|
||||
for X := 0 to N - 1 do
|
||||
begin
|
||||
if X >= Target.Width then Break;
|
||||
S := RS^; D := RD^;
|
||||
RD^ := ((((S and $00FF00FF) * A256 + (D and $00FF00FF) * I256) shr 8)
|
||||
and $00FF00FF)
|
||||
or ((((S shr 8) and $00FF00FF) * A256 +
|
||||
((D shr 8) and $00FF00FF) * I256) and $FF00FF00)
|
||||
{$IFDEF DARWIN}
|
||||
RD[1] := Byte((A * RS[1] + InvA * RD[1]) div 255);
|
||||
RD[2] := Byte((A * RS[2] + InvA * RD[2]) div 255);
|
||||
RD[3] := Byte((A * RS[3] + InvA * RD[3]) div 255);
|
||||
or $000000FF; // ARGB: альфа в байте 0
|
||||
{$ELSE}
|
||||
RD[0] := Byte((A * RS[0] + InvA * RD[0]) div 255);
|
||||
RD[1] := Byte((A * RS[1] + InvA * RD[1]) div 255);
|
||||
RD[2] := Byte((A * RS[2] + InvA * RD[2]) div 255);
|
||||
or $FF000000; // BGRA: альфа в байте 3
|
||||
{$ENDIF}
|
||||
Inc(RS, 4); Inc(RD, 4);
|
||||
Inc(RS); Inc(RD);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
|
||||
Reference in New Issue
Block a user