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:
2026-07-03 00:11:31 +03:00
co-authored by Claude Fable 5
parent 0b4d126dac
commit 0adaff2d21
7 changed files with 617 additions and 247 deletions
+29 -21
View File
@@ -273,9 +273,13 @@ const
DB_S: array[1..9] of Double = (-121,-115,-109,-103,-97,-91,-85,-79,-73);
procedure BlendBitmapKey(Target, Source: TBitmap; DstX, DstY: Integer; Alpha: Byte);
// Дворд-блендинг (два 16-битных лейна на умножение, /256 вместо /255 —
// расхождение <0.4%): побайтовый вариант с div 255 на 2 флагах стоил ~0.6мс.
// Keyed-пиксели (магента) пропускаются сравнением цветовых байтов дворда.
var
Y, X, SX0, SY0, SX1, SY1, TY, InvA: Integer;
SrcRow, DstRow: PByte;
Y, X, SX0, SY0, SX1, SY1, TY: Integer;
A256, I256, S, D: LongWord;
SrcRow, DstRow: PLongWord;
begin
if (Target = nil) or (Source = nil) or (Alpha = 0) then Exit;
SX0 := 0; SY0 := 0;
@@ -286,37 +290,41 @@ begin
if DstY + (SY1 - SY0) > Target.Height then SY1 := SY0 + Target.Height - DstY;
if (SX1 <= SX0) or (SY1 <= SY0) then Exit;
InvA := 255 - Alpha;
A256 := Alpha + (Alpha shr 7); // 0..256
I256 := 256 - A256;
Target.BeginUpdate(False);
Source.BeginUpdate(False);
try
for Y := SY0 to SY1 - 1 do
begin
TY := DstY + (Y - SY0);
SrcRow := PByte(Source.ScanLine[Y]);
DstRow := PByte(Target.ScanLine[TY]);
SrcRow := PLongWord(Source.ScanLine[Y]);
DstRow := PLongWord(Target.ScanLine[TY]);
if (SrcRow = nil) or (DstRow = nil) then Continue;
Inc(SrcRow, SX0 * 4);
Inc(DstRow, DstX * 4);
Inc(SrcRow, SX0);
Inc(DstRow, DstX);
for X := SX0 to SX1 - 1 do
begin
S := SrcRow^;
{$IFDEF DARWIN}
if not ((SrcRow[1] = $FF) and (SrcRow[2] = $00) and (SrcRow[3] = $FF)) then
begin
DstRow[1] := Byte((Alpha * SrcRow[1] + InvA * DstRow[1]) div 255);
DstRow[2] := Byte((Alpha * SrcRow[2] + InvA * DstRow[2]) div 255);
DstRow[3] := Byte((Alpha * SrcRow[3] + InvA * DstRow[3]) div 255);
end;
if (S and $FFFFFF00) <> $FF00FF00 then // ARGB: R=FF,G=00,B=FF
{$ELSE}
if not ((SrcRow[0] = $FF) and (SrcRow[1] = $00) and (SrcRow[2] = $FF)) then
begin
DstRow[0] := Byte((Alpha * SrcRow[0] + InvA * DstRow[0]) div 255);
DstRow[1] := Byte((Alpha * SrcRow[1] + InvA * DstRow[1]) div 255);
DstRow[2] := Byte((Alpha * SrcRow[2] + InvA * DstRow[2]) div 255);
end;
if (S and $00FFFFFF) <> $00FF00FF then // BGRA: B=FF,G=00,R=FF
{$ENDIF}
Inc(SrcRow, 4);
Inc(DstRow, 4);
begin
D := DstRow^;
DstRow^ := ((((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}
or $000000FF;
{$ELSE}
or $FF000000;
{$ENDIF}
end;
Inc(SrcRow);
Inc(DstRow);
end;
end;
finally