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
+28 -19
View File
@@ -383,7 +383,12 @@ end;
// Альфа-композит FCache поверх Target в точке (FLeft, FTop) — один проход/кадр.
procedure TSampleRateOverlay.CompositeCache(Target: TBitmap);
var X, Y, A, InvA, h: Integer; RS, RD: PByte;
// Дворд-блендинг (два 16-битных лейна на умножение, /256 вместо /255) —
// per-pixel альфа из альфа-байта кэша. Альфа-байт цели добивается до $FF.
var
X, Y, h: Integer;
A256, I256, S, D: LongWord;
RS, RD: PLongWord;
begin
if (Target = nil) or (FCacheW <= 0) then Exit;
h := FCache.Height;
@@ -393,33 +398,37 @@ begin
for Y := 0 to h - 1 do
begin
if (FTop + Y < 0) or (FTop + Y >= Target.Height) then Continue;
RS := PByte(FCache.ScanLine[Y]);
RD := PByte(Target.ScanLine[FTop + Y]);
RS := PLongWord(FCache.ScanLine[Y]);
RD := PLongWord(Target.ScanLine[FTop + Y]);
if (RS = nil) or (RD = nil) then Continue;
Inc(RD, FLeft * 4);
Inc(RD, FLeft);
for X := 0 to FCacheW - 1 do
begin
if (FLeft + X >= 0) and (FLeft + X < Target.Width) then
begin
S := RS^;
{$IFDEF DARWIN}
A := RS[0]; InvA := 255 - A;
if A > 0 then
begin
RD[1] := Byte((RS[1]*A + RD[1]*InvA) div 255);
RD[2] := Byte((RS[2]*A + RD[2]*InvA) div 255);
RD[3] := Byte((RS[3]*A + RD[3]*InvA) div 255);
end;
A256 := S and $FF; // ARGB: альфа в байте 0
{$ELSE}
A := RS[3]; InvA := 255 - A;
if A > 0 then
begin
RD[0] := Byte((RS[0]*A + RD[0]*InvA) div 255);
RD[1] := Byte((RS[1]*A + RD[1]*InvA) div 255);
RD[2] := Byte((RS[2]*A + RD[2]*InvA) div 255);
end;
A256 := S shr 24; // BGRA: альфа в байте 3
{$ENDIF}
if A256 > 0 then
begin
A256 := A256 + (A256 shr 7); // 0..256
I256 := 256 - A256;
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}
or $000000FF;
{$ELSE}
or $FF000000;
{$ENDIF}
end;
end;
Inc(RS, 4); Inc(RD, 4);
Inc(RS); Inc(RD);
end;
end;
finally