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:
+519
-183
@@ -18,7 +18,7 @@ unit SpectrumView;
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
||||
Classes, SysUtils, Graphics, GraphType, ExtCtrls, Controls, Math,
|
||||
AppTheme,
|
||||
AlertOverlay, SampleRateOverlay, VfoOverlay, BandPlanOverlay,
|
||||
WaterfallView, SMeterView, RulerView,
|
||||
@@ -28,6 +28,15 @@ const
|
||||
SV_CLR_BG = TColor($00101010);
|
||||
|
||||
type
|
||||
// Кэш текстовой маски для raw-рендера: строка отрисована белым на чёрном
|
||||
// (canvas — один раз при создании), при блите канал покрытия умножается
|
||||
// на нужный цвет. Цвет в ключ не входит.
|
||||
TRawTextMask = record
|
||||
Key: string; // '<size>|<bold>|<text>'
|
||||
Bmp: TBitmap;
|
||||
LastUse: Int64;
|
||||
end;
|
||||
|
||||
TSpectrumView = class
|
||||
protected
|
||||
// ── Off-screen bitmaps ────────────────────────────────────────────────────
|
||||
@@ -41,6 +50,13 @@ type
|
||||
FSpPts: array of TPoint;
|
||||
FSpPtsLen: Integer;
|
||||
FFillSpectrum: Boolean;
|
||||
// ── Full-raw рендер кадра (без Canvas на битмапе спектра) ────────────────
|
||||
FRawBase: PByte; // база пикселей кадра; валидна между RawBegin/RawEnd
|
||||
FRawBPL: Integer; // bytes per line (отрицателен при bottom-up DIB)
|
||||
FRawW: Integer;
|
||||
FRawH: Integer;
|
||||
FTextMasks: array of TRawTextMask;
|
||||
FTextTick: Int64;
|
||||
// ── Тема ─────────────────────────────────────────────────────────────────
|
||||
FTheme: TAppTheme;
|
||||
FLightTheme: Boolean;
|
||||
@@ -109,14 +125,31 @@ type
|
||||
procedure SetMarkerX(V: Integer);
|
||||
procedure SetFillSpectrum(V: Boolean);
|
||||
procedure DrawSpectrumGradient(const SpPts: array of TPoint; W, H: Integer);
|
||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||
// Слайс-маркеры разделены на raw-часть (полосы, ScanLine) и canvas-часть
|
||||
// (линии/буквы): DrawSpectrum группирует raw- и canvas-операции, чтобы
|
||||
// не гонять Qt6-битмап между режимами доступа (дорогая синхронизация).
|
||||
// Full-raw рендер кадра: ВСЕ операции на битмапе спектра идут через
|
||||
// прямой доступ к пикселям (RawBegin/RawEnd), Canvas на нём не трогаем —
|
||||
// на Qt6 каждый переход raw(ScanLine)<->canvas(QPainter) синкает весь
|
||||
// битмап (~2мс), поэтому линии/пунктир/кривая рисуются вручную, а тексты
|
||||
// блитятся из кэша масок (canvas-рендер строки один раз при изменении).
|
||||
procedure RawBegin;
|
||||
procedure RawEnd;
|
||||
procedure RawVLine(X, Y1, Y2: Integer; Color: TColor; LineW: Integer = 1;
|
||||
OnPx: Integer = 0; OffPx: Integer = 0);
|
||||
procedure RawHLine(Y, X1, X2: Integer; Color: TColor;
|
||||
OnPx: Integer = 0; OffPx: Integer = 0);
|
||||
procedure RawFillRect(X1, Y1, X2, Y2: Integer; Color: TColor);
|
||||
procedure RawTriangleDown(CX, HalfW, Hgt: Integer; Color: TColor);
|
||||
procedure RawCurve(W: Integer; Color: TColor);
|
||||
function GetTextMask(const S: string; FontSize: Integer;
|
||||
Bold: Boolean): TBitmap;
|
||||
function RawTextWidth(const S: string; FontSize: Integer;
|
||||
Bold: Boolean): Integer;
|
||||
procedure RawText(X, Y: Integer; const S: string; Color: TColor;
|
||||
FontSize: Integer; Bold: Boolean; BgColor: TColor);
|
||||
procedure DrawMarkerLineRaw(W, H: Integer);
|
||||
procedure DrawSliceFilterBands(W, H: Integer);
|
||||
procedure DrawSliceFilterLines(C: TCanvas; W, H: Integer);
|
||||
procedure DrawBandLetter(C: TCanvas; X1, X2: Integer; L: Char; Clr: TColor);
|
||||
procedure DrawBeaconMarkers(C: TCanvas; W, H: Integer);
|
||||
procedure DrawSliceFilterLinesRaw(W, H: Integer);
|
||||
procedure DrawBandLetterRaw(X1, X2: Integer; L: Char; Clr: TColor);
|
||||
procedure DrawBeaconMarkersRaw(W, H: Integer);
|
||||
procedure BlendBand(X1, X2, H: Integer; R, G, B, Alpha: Byte);
|
||||
procedure FillBandRaw(X1, X2, H: Integer; Color: TColor);
|
||||
procedure CopyGridToSpectrum(W, H: Integer);
|
||||
@@ -273,10 +306,20 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
var
|
||||
// ВРЕМЕННО (perf/cpu-profiling): EWSDR_ALPHA_FIX=0 → пропустить альфа-OR
|
||||
// проход в DrawSpectrum (проверка, нужен ли он на Qt6).
|
||||
GSkipAlphaFix: Boolean = False;
|
||||
// Упаковка TColor в пиксель кадра (непрозрачный). Раскладка байтов как в
|
||||
// BlendBand: non-Darwin = BGRA, Darwin = ARGB.
|
||||
function RawPack(Color: TColor): LongWord; inline;
|
||||
var R, G, B: Byte;
|
||||
begin
|
||||
R := Color and $FF; G := (Color shr 8) and $FF; B := (Color shr 16) and $FF;
|
||||
{$IFDEF DARWIN}
|
||||
Result := LongWord($FF) or (LongWord(R) shl 8) or (LongWord(G) shl 16) or
|
||||
(LongWord(B) shl 24);
|
||||
{$ELSE}
|
||||
Result := LongWord(B) or (LongWord(G) shl 8) or (LongWord(R) shl 16) or
|
||||
$FF000000;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Вспомогательные функции
|
||||
@@ -358,7 +401,10 @@ begin
|
||||
end;
|
||||
|
||||
destructor TSpectrumView.Destroy;
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 0 to High(FTextMasks) do
|
||||
FTextMasks[i].Bmp.Free;
|
||||
FSpectrumBitmap.Free;
|
||||
FGridBitmap.Free;
|
||||
FWaterfall.Free;
|
||||
@@ -529,22 +575,24 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||
var MX: Integer; MarkerFreq: Double; MarkerLbl: string;
|
||||
procedure TSpectrumView.DrawMarkerLineRaw(W, H: Integer);
|
||||
const
|
||||
MARK_CLR = TColor($004444FF);
|
||||
var
|
||||
MX, LblW: Integer;
|
||||
MarkerFreq: Double;
|
||||
MarkerLbl: string;
|
||||
begin
|
||||
MX := Round(FMarkerX / 1000.0 * W);
|
||||
if (MX < 0) or (MX >= W) then Exit;
|
||||
MarkerFreq := (FCenterFreq - FSpanHz / 2) + FMarkerX / 1000.0 * FSpanHz;
|
||||
MarkerLbl := FormatFreqSV(Round(MarkerFreq));
|
||||
C.Pen.Color := TColor($004444FF);
|
||||
C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(MX, 0); C.LineTo(MX, H);
|
||||
C.Font.Color := TColor($004444FF);
|
||||
C.Font.Size := 7; C.Font.Name := 'Courier New';
|
||||
if MX + 4 + C.TextWidth(MarkerLbl) < W then
|
||||
C.TextOut(MX + 4, 4, MarkerLbl)
|
||||
RawVLine(MX, 0, H - 1, MARK_CLR);
|
||||
LblW := RawTextWidth(MarkerLbl, 7, False);
|
||||
if MX + 4 + LblW < W then
|
||||
RawText(MX + 4, 4, MarkerLbl, MARK_CLR, 7, False, clNone)
|
||||
else
|
||||
C.TextOut(MX - 4 - C.TextWidth(MarkerLbl), 4, MarkerLbl);
|
||||
RawText(MX - 4 - LblW, 4, MarkerLbl, MARK_CLR, 7, False, clNone);
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.SetBeaconMarkers(Active: Boolean; RefHz, TrkHz: Double);
|
||||
@@ -561,34 +609,293 @@ begin
|
||||
FBeaconDecHalf := HalfHz;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.DrawBeaconMarkers(C: TCanvas; W, H: Integer);
|
||||
procedure TSpectrumView.DrawBeaconMarkersRaw(W, H: Integer);
|
||||
// Две вертикали: опорная частота маяка (зелёная пунктир) и отслеживаемый
|
||||
// центроид (оранжевая сплошная). Расхождение видно глазом → понятно, сел ли
|
||||
// контур на пик маяка или на шум.
|
||||
procedure Vline(FreqHz: Double; Col: TColor; Style: TPenStyle);
|
||||
procedure Vline(FreqHz: Double; Col: TColor; Dotted: Boolean);
|
||||
var x: Integer;
|
||||
begin
|
||||
if FSpanHz <= 0 then Exit;
|
||||
x := Round((FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
|
||||
if (x < 0) or (x >= W) then Exit;
|
||||
C.Pen.Color := Col; C.Pen.Width := 1; C.Pen.Style := Style;
|
||||
C.MoveTo(x, 0); C.LineTo(x, H);
|
||||
if Dotted then RawVLine(x, 0, H - 1, Col, 1, 1, 2)
|
||||
else RawVLine(x, 0, H - 1, Col);
|
||||
end;
|
||||
begin
|
||||
if FBeaconMarkActive then
|
||||
begin
|
||||
Vline(FBeaconRefFreq, clLime, psDot); // где маяк ДОЛЖЕН быть
|
||||
Vline(FBeaconTrkFreq, TColor($000AA5FF), psSolid); // отслеживаемый центроид (оранж)
|
||||
Vline(FBeaconRefFreq, clLime, True); // где маяк ДОЛЖЕН быть
|
||||
Vline(FBeaconTrkFreq, TColor($000AA5FF), False); // отслеживаемый центроид (оранж)
|
||||
end;
|
||||
// Узкий фильтр-маркер декодера: грани полосы + центральная линия. Три линии без
|
||||
// полупрозрачной заливки (как в GL-рендере) — единый вид и дешевле для CPU.
|
||||
if FBeaconDecActive and (FSpanHz > 0) then
|
||||
begin
|
||||
Vline(FBeaconDecFreq - FBeaconDecHalf, TColor($0020D0FF), psSolid); // грань фильтра
|
||||
Vline(FBeaconDecFreq + FBeaconDecHalf, TColor($0020D0FF), psSolid);
|
||||
Vline(FBeaconDecFreq, TColor($0020D0FF), psSolid); // центр наведения
|
||||
Vline(FBeaconDecFreq - FBeaconDecHalf, TColor($0020D0FF), False); // грань фильтра
|
||||
Vline(FBeaconDecFreq + FBeaconDecHalf, TColor($0020D0FF), False);
|
||||
Vline(FBeaconDecFreq, TColor($0020D0FF), False); // центр наведения
|
||||
end;
|
||||
end;
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// Full-raw примитивы кадра (валидны только между RawBegin/RawEnd)
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
procedure TSpectrumView.RawBegin;
|
||||
var RI: TRawImage;
|
||||
begin
|
||||
FSpectrumBitmap.BeginUpdate(False);
|
||||
RI := FSpectrumBitmap.RawImage;
|
||||
FRawW := FSpectrumBitmap.Width;
|
||||
FRawH := FSpectrumBitmap.Height;
|
||||
FRawBPL := RI.Description.BytesPerLine;
|
||||
FRawBase := RI.Data;
|
||||
if (FRawBase <> nil) and
|
||||
(RI.Description.LineOrder = riloBottomToTop) then
|
||||
begin
|
||||
// win32 DIB: строки снизу вверх — идём с последней с отрицательным шагом
|
||||
FRawBase := FRawBase + (FRawH - 1) * FRawBPL;
|
||||
FRawBPL := -FRawBPL;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RawEnd;
|
||||
begin
|
||||
FRawBase := nil;
|
||||
FSpectrumBitmap.EndUpdate(False);
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RawVLine(X, Y1, Y2: Integer; Color: TColor;
|
||||
LineW: Integer; OnPx: Integer; OffPx: Integer);
|
||||
var
|
||||
P: PLongWord;
|
||||
RowP: PByte;
|
||||
Y, K, Period: Integer;
|
||||
Px: LongWord;
|
||||
begin
|
||||
if FRawBase = nil then Exit;
|
||||
if X < 0 then begin Inc(LineW, X); X := 0; end;
|
||||
if X + LineW > FRawW then LineW := FRawW - X;
|
||||
if LineW <= 0 then Exit;
|
||||
if Y1 < 0 then Y1 := 0;
|
||||
if Y2 > FRawH - 1 then Y2 := FRawH - 1;
|
||||
if Y2 < Y1 then Exit;
|
||||
Px := RawPack(Color);
|
||||
Period := OnPx + OffPx;
|
||||
RowP := FRawBase + Y1 * FRawBPL + X * 4;
|
||||
for Y := Y1 to Y2 do
|
||||
begin
|
||||
if (Period = 0) or ((Y mod Period) < OnPx) then
|
||||
begin
|
||||
P := PLongWord(RowP);
|
||||
for K := 1 to LineW do begin P^ := Px; Inc(P); end;
|
||||
end;
|
||||
Inc(RowP, FRawBPL);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RawHLine(Y, X1, X2: Integer; Color: TColor;
|
||||
OnPx: Integer; OffPx: Integer);
|
||||
var
|
||||
P: PLongWord;
|
||||
X, Period: Integer;
|
||||
Px: LongWord;
|
||||
begin
|
||||
if FRawBase = nil then Exit;
|
||||
if (Y < 0) or (Y >= FRawH) then Exit;
|
||||
if X1 < 0 then X1 := 0;
|
||||
if X2 > FRawW then X2 := FRawW;
|
||||
if X2 <= X1 then Exit;
|
||||
Px := RawPack(Color);
|
||||
Period := OnPx + OffPx;
|
||||
P := PLongWord(FRawBase + Y * FRawBPL + X1 * 4);
|
||||
for X := X1 to X2 - 1 do
|
||||
begin
|
||||
if (Period = 0) or ((X mod Period) < OnPx) then P^ := Px;
|
||||
Inc(P);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RawFillRect(X1, Y1, X2, Y2: Integer; Color: TColor);
|
||||
var
|
||||
Y: Integer;
|
||||
Px: LongWord;
|
||||
begin
|
||||
if FRawBase = nil then Exit;
|
||||
if X1 < 0 then X1 := 0;
|
||||
if Y1 < 0 then Y1 := 0;
|
||||
if X2 > FRawW then X2 := FRawW;
|
||||
if Y2 > FRawH then Y2 := FRawH;
|
||||
if (X2 <= X1) or (Y2 <= Y1) then Exit;
|
||||
Px := RawPack(Color);
|
||||
for Y := Y1 to Y2 - 1 do
|
||||
FillDWord((FRawBase + Y * FRawBPL + X1 * 4)^, X2 - X1, Px);
|
||||
end;
|
||||
|
||||
// Треугольник остриём вниз (курсор VFO): вершина основания на Y=0.
|
||||
procedure TSpectrumView.RawTriangleDown(CX, HalfW, Hgt: Integer; Color: TColor);
|
||||
var
|
||||
Y, Half: Integer;
|
||||
begin
|
||||
for Y := 0 to Hgt do
|
||||
begin
|
||||
Half := Round(HalfW * (Hgt - Y) / Hgt);
|
||||
RawHLine(Y, CX - Half, CX + Half + 1, Color);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Кривая спектра: вертикальный сегмент в каждой колонке от Y предыдущей
|
||||
// точки до текущей — классический connected-line без Canvas.Polyline.
|
||||
procedure TSpectrumView.RawCurve(W: Integer; Color: TColor);
|
||||
var
|
||||
X, Cur, Prev, Y0, Y1, Y: Integer;
|
||||
Px: LongWord;
|
||||
P: PByte;
|
||||
begin
|
||||
if (FRawBase = nil) or (W < 1) or (Length(FSpPts) < W) then Exit;
|
||||
if W > FRawW then W := FRawW;
|
||||
Px := RawPack(Color);
|
||||
Prev := FSpPts[0].Y;
|
||||
for X := 0 to W - 1 do
|
||||
begin
|
||||
Cur := FSpPts[X].Y;
|
||||
if Cur < Prev then begin Y0 := Cur; Y1 := Prev; end
|
||||
else begin Y0 := Prev; Y1 := Cur; end;
|
||||
if Y0 < 0 then Y0 := 0;
|
||||
if Y1 > FRawH - 1 then Y1 := FRawH - 1;
|
||||
P := FRawBase + Y0 * FRawBPL + X * 4;
|
||||
for Y := Y0 to Y1 do
|
||||
begin
|
||||
PLongWord(P)^ := Px;
|
||||
Inc(P, FRawBPL);
|
||||
end;
|
||||
Prev := Cur;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TSpectrumView.GetTextMask(const S: string; FontSize: Integer;
|
||||
Bold: Boolean): TBitmap;
|
||||
const
|
||||
MAX_MASKS = 48;
|
||||
var
|
||||
i, Oldest: Integer;
|
||||
Key: string;
|
||||
B: TBitmap;
|
||||
TW, TH: Integer;
|
||||
|
||||
procedure ApplyFont(Cv: TCanvas);
|
||||
begin
|
||||
Cv.Font.Name := 'Courier New';
|
||||
Cv.Font.Size := FontSize;
|
||||
if Bold then Cv.Font.Style := [fsBold] else Cv.Font.Style := [];
|
||||
end;
|
||||
|
||||
begin
|
||||
Inc(FTextTick);
|
||||
Key := Format('%d|%d|%s', [FontSize, Ord(Bold), S]);
|
||||
for i := 0 to High(FTextMasks) do
|
||||
if FTextMasks[i].Key = Key then
|
||||
begin
|
||||
FTextMasks[i].LastUse := FTextTick;
|
||||
Exit(FTextMasks[i].Bmp);
|
||||
end;
|
||||
|
||||
// Рендер маски: белый текст на чёрном (единственное место, где текст
|
||||
// проходит через Canvas — на собственном маленьком битмапе, один раз).
|
||||
B := TBitmap.Create;
|
||||
B.PixelFormat := pf32bit;
|
||||
B.SetSize(4, 4);
|
||||
ApplyFont(B.Canvas);
|
||||
TW := Max(1, B.Canvas.TextWidth(S));
|
||||
TH := Max(1, B.Canvas.TextHeight(S));
|
||||
B.SetSize(TW, TH);
|
||||
ApplyFont(B.Canvas);
|
||||
B.Canvas.Brush.Color := clBlack; B.Canvas.Brush.Style := bsSolid;
|
||||
B.Canvas.FillRect(Rect(0, 0, TW, TH));
|
||||
B.Canvas.Font.Color := clWhite;
|
||||
B.Canvas.Brush.Style := bsClear;
|
||||
B.Canvas.TextOut(0, 0, S);
|
||||
|
||||
if Length(FTextMasks) >= MAX_MASKS then
|
||||
begin
|
||||
// LRU-вытеснение (маркерная метка при drag плодит новые строки)
|
||||
Oldest := 0;
|
||||
for i := 1 to High(FTextMasks) do
|
||||
if FTextMasks[i].LastUse < FTextMasks[Oldest].LastUse then Oldest := i;
|
||||
FTextMasks[Oldest].Bmp.Free;
|
||||
FTextMasks[Oldest].Key := Key;
|
||||
FTextMasks[Oldest].Bmp := B;
|
||||
FTextMasks[Oldest].LastUse := FTextTick;
|
||||
end else
|
||||
begin
|
||||
SetLength(FTextMasks, Length(FTextMasks) + 1);
|
||||
FTextMasks[High(FTextMasks)].Key := Key;
|
||||
FTextMasks[High(FTextMasks)].Bmp := B;
|
||||
FTextMasks[High(FTextMasks)].LastUse := FTextTick;
|
||||
end;
|
||||
Result := B;
|
||||
end;
|
||||
|
||||
function TSpectrumView.RawTextWidth(const S: string; FontSize: Integer;
|
||||
Bold: Boolean): Integer;
|
||||
begin
|
||||
Result := GetTextMask(S, FontSize, Bold).Width;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RawText(X, Y: Integer; const S: string; Color: TColor;
|
||||
FontSize: Integer; Bold: Boolean; BgColor: TColor);
|
||||
var
|
||||
M: TBitmap;
|
||||
MX, MY, TY, A, InvA: Integer;
|
||||
SrcRow, P: PByte;
|
||||
CR, CG, CB: Byte;
|
||||
begin
|
||||
if FRawBase = nil then Exit;
|
||||
M := GetTextMask(S, FontSize, Bold);
|
||||
if BgColor <> clNone then
|
||||
RawFillRect(X, Y, X + M.Width, Y + M.Height, BgColor);
|
||||
CR := Color and $FF; CG := (Color shr 8) and $FF; CB := (Color shr 16) and $FF;
|
||||
M.BeginUpdate(False);
|
||||
try
|
||||
for MY := 0 to M.Height - 1 do
|
||||
begin
|
||||
TY := Y + MY;
|
||||
if (TY < 0) or (TY >= FRawH) then Continue;
|
||||
SrcRow := PByte(M.ScanLine[MY]);
|
||||
if SrcRow = nil then Continue;
|
||||
for MX := 0 to M.Width - 1 do
|
||||
begin
|
||||
if (X + MX >= 0) and (X + MX < FRawW) then
|
||||
begin
|
||||
{$IFDEF DARWIN}
|
||||
A := SrcRow[2]; // G-канал маски (текст белый ⇒ покрытие)
|
||||
{$ELSE}
|
||||
A := SrcRow[1];
|
||||
{$ENDIF}
|
||||
if A > 0 then
|
||||
begin
|
||||
InvA := 255 - A;
|
||||
P := FRawBase + TY * FRawBPL + (X + MX) * 4;
|
||||
{$IFDEF DARWIN}
|
||||
P[1] := Byte((CR * A + P[1] * InvA) div 255);
|
||||
P[2] := Byte((CG * A + P[2] * InvA) div 255);
|
||||
P[3] := Byte((CB * A + P[3] * InvA) div 255);
|
||||
P[0] := $FF;
|
||||
{$ELSE}
|
||||
P[0] := Byte((CB * A + P[0] * InvA) div 255);
|
||||
P[1] := Byte((CG * A + P[1] * InvA) div 255);
|
||||
P[2] := Byte((CR * A + P[2] * InvA) div 255);
|
||||
P[3] := $FF;
|
||||
{$ENDIF}
|
||||
end;
|
||||
end;
|
||||
Inc(SrcRow, 4);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
M.EndUpdate(False);
|
||||
end;
|
||||
C.Pen.Style := psSolid;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.BlendBand(X1, X2, H: Integer; R, G, B, Alpha: Byte);
|
||||
@@ -689,7 +996,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.DrawSliceFilterLines(C: TCanvas; W, H: Integer);
|
||||
procedure TSpectrumView.DrawSliceFilterLinesRaw(W, H: Integer);
|
||||
const
|
||||
SLICE_CLR = TColor($0010A8F0);
|
||||
var
|
||||
@@ -701,33 +1008,24 @@ begin
|
||||
O := TVfoOverlay(FSliceOverlays[i]);
|
||||
if (O = nil) or (not O.Visible) then Continue;
|
||||
CalcFilterBandXFor(O.DisplayVfoHz, O.DisplayMode, O.DisplayBW, W, SX1, SX2, SVfoX);
|
||||
C.Pen.Color := SLICE_CLR; C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(SX1, 0); C.LineTo(SX1, H);
|
||||
C.MoveTo(SX2, 0); C.LineTo(SX2, H);
|
||||
C.Pen.Width := 2;
|
||||
C.MoveTo(SVfoX, 0); C.LineTo(SVfoX, H - 12);
|
||||
C.Pen.Width := 1;
|
||||
RawVLine(SX1, 0, H - 1, SLICE_CLR);
|
||||
RawVLine(SX2, 0, H - 1, SLICE_CLR);
|
||||
RawVLine(SVfoX, 0, H - 13, SLICE_CLR, 2); // несущая (жирнее)
|
||||
// Буква слайса внутри полосы фильтра сверху — привязка полосы к флагу,
|
||||
// цвет = цвет слайса (совпадает с бейджем во флаге).
|
||||
DrawBandLetter(C, SX1, SX2, O.SliceLetter, SliceColor(O.SliceLetter));
|
||||
DrawBandLetterRaw(SX1, SX2, O.SliceLetter, SliceColor(O.SliceLetter));
|
||||
end;
|
||||
end;
|
||||
|
||||
// Буква слайса (A/B/…) по центру полосы фильтра, у самого верха — чтобы
|
||||
// понять, какому флагу принадлежит полоса, даже когда флаги стоят рядом.
|
||||
procedure TSpectrumView.DrawBandLetter(C: TCanvas; X1, X2: Integer;
|
||||
L: Char; Clr: TColor);
|
||||
procedure TSpectrumView.DrawBandLetterRaw(X1, X2: Integer; L: Char; Clr: TColor);
|
||||
var
|
||||
cx: Integer;
|
||||
begin
|
||||
if (L < 'A') or (L > 'Z') then Exit;
|
||||
cx := (X1 + X2) div 2;
|
||||
C.Font.Name := 'Courier New'; C.Font.Size := 8; C.Font.Style := [fsBold];
|
||||
C.Font.Color := Clr;
|
||||
C.Brush.Style := bsClear;
|
||||
C.TextOut(cx - C.TextWidth(L) div 2, 0, L);
|
||||
C.Brush.Style := bsSolid;
|
||||
C.Font.Style := [];
|
||||
RawText(cx - RawTextWidth(L, 8, True) div 2, 0, L, Clr, 8, True, clNone);
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.CopyGridToSpectrum(W, H: Integer);
|
||||
@@ -789,10 +1087,15 @@ begin
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.DrawSpectrumGradient(const SpPts: array of TPoint; W, H: Integer);
|
||||
// Дворд-блендинг: цвет и альфа постоянны в пределах строки → произведения
|
||||
// source-лейнов считаются один раз на строку, в пиксельном цикле остаются
|
||||
// два умножения на dst-лейны (/256 вместо /200-базы оригинала, кривая
|
||||
// прозрачности та же — квадратичная). Побайтовый вариант стоил ~1.6мс/кадр.
|
||||
var
|
||||
Row, Col, Alpha, YMin, Off: Integer;
|
||||
RowPtr: PByte;
|
||||
Row, Col, Alpha, YMin, A256, I256: Integer;
|
||||
RowPtr: PLongWord;
|
||||
GradB, GradG, GradR: Byte;
|
||||
SrcPx, SrcLo, SrcHi, D: LongWord;
|
||||
begin
|
||||
if FSpectrumBitmap = nil then Exit;
|
||||
if (W <= 0) or (H <= 0) then Exit;
|
||||
@@ -810,21 +1113,31 @@ begin
|
||||
GradB := Byte(FTheme.SpecGradB * Alpha div 200);
|
||||
GradG := Byte(FTheme.SpecGradG * Alpha div 200);
|
||||
GradR := Byte(FTheme.SpecGradR * Alpha div 200);
|
||||
RowPtr := PByte(FSpectrumBitmap.ScanLine[Row]);
|
||||
// Альфа 0..200 → 0..256; source-лейны умножаются здесь, один раз на строку
|
||||
A256 := (Alpha * 256 + 100) div 200;
|
||||
I256 := 256 - A256;
|
||||
SrcPx := RawPack(TColor(GradR or (LongWord(GradG) shl 8) or
|
||||
(LongWord(GradB) shl 16)));
|
||||
SrcLo := (SrcPx and $00FF00FF) * LongWord(A256);
|
||||
SrcHi := ((SrcPx shr 8) and $00FF00FF) * LongWord(A256);
|
||||
RowPtr := PLongWord(FSpectrumBitmap.ScanLine[Row]);
|
||||
for Col := 0 to W - 1 do
|
||||
begin
|
||||
if SpPts[Col].Y <= Row then
|
||||
begin
|
||||
Off := Col * 4;
|
||||
D := RowPtr^;
|
||||
RowPtr^ := (((SrcLo + (D and $00FF00FF) * LongWord(I256)) shr 8)
|
||||
and $00FF00FF)
|
||||
or ((SrcHi + ((D shr 8) and $00FF00FF) * LongWord(I256))
|
||||
and $FF00FF00)
|
||||
{$IFDEF DARWIN}
|
||||
RowPtr[Off+1] := (GradR * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
|
||||
RowPtr[Off+2] := (GradG * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
|
||||
RowPtr[Off+3] := (GradB * Alpha + RowPtr[Off+3] * (200 - Alpha)) div 200;
|
||||
or $000000FF;
|
||||
{$ELSE}
|
||||
RowPtr[Off] := (GradB * Alpha + RowPtr[Off] * (200 - Alpha)) div 200;
|
||||
RowPtr[Off+1] := (GradG * Alpha + RowPtr[Off+1] * (200 - Alpha)) div 200;
|
||||
RowPtr[Off+2] := (GradR * Alpha + RowPtr[Off+2] * (200 - Alpha)) div 200;
|
||||
or $FF000000;
|
||||
{$ENDIF}
|
||||
end;
|
||||
Inc(RowPtr);
|
||||
end;
|
||||
end;
|
||||
FSpectrumBitmap.EndUpdate(False);
|
||||
end;
|
||||
@@ -1046,6 +1359,23 @@ begin
|
||||
GC.MoveTo(GX, 0); GC.LineTo(GX, H);
|
||||
end;
|
||||
end;
|
||||
{$IFNDEF DARWIN}
|
||||
// Альфа кэша сетки → $FF один раз после ребилда: canvas-текст/AA на Qt6
|
||||
// оставляют битую альфу, а кадр копирует её memcpy как есть. Пофреймовый
|
||||
// OR-проход по кадру удалён — на битмапе кадра Canvas больше не бывает.
|
||||
FGridBitmap.BeginUpdate(False);
|
||||
for i := 0 to H - 1 do
|
||||
begin
|
||||
RowLW := PLongWord(FGridBitmap.ScanLine[i]);
|
||||
if RowLW = nil then Continue;
|
||||
for Yp := 0 to W - 1 do
|
||||
begin
|
||||
RowLW^ := RowLW^ or $FF000000;
|
||||
Inc(RowLW);
|
||||
end;
|
||||
end;
|
||||
FGridBitmap.EndUpdate(False);
|
||||
{$ENDIF}
|
||||
end;
|
||||
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: ребилд кэша сетки (обычно 0)
|
||||
PerfTz := PerfNow; // PERF
|
||||
@@ -1104,131 +1434,139 @@ begin
|
||||
PerfAdd(pzUiSpecCurve, PerfTz); // PERF: расчёт точек кривой
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// ═══ Фаза 1: raw (ScanLine) — сетка, полосы, градиент ════════════════════
|
||||
// ═══ Фаза 1: raw — весь кадр одним локом ═════════════════════════════════
|
||||
{$IFDEF DARWIN}
|
||||
// Cocoa-вариант CopyGridToSpectrum идёт через Canvas.Draw — до RawBegin.
|
||||
CopyGridToSpectrum(W, H);
|
||||
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный memcpy сетки в битмап
|
||||
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный перенос сетки в битмап
|
||||
PerfTz := PerfNow; // PERF
|
||||
{$ENDIF}
|
||||
|
||||
if FTXOverlay then
|
||||
begin
|
||||
if FTXVfoIndex <> FActiveVfo then
|
||||
begin
|
||||
if X2 > X1 then BlendBand(X1, X2, H, $30, $C0, $30, 100);
|
||||
if TXX2 > TXX1 then BlendBand(TXX1, TXX2, H, $E0, $30, $30, 100);
|
||||
end else
|
||||
if X2 > X1 then BlendBand(X1, X2, H, $E0, $30, $30, 100);
|
||||
end else
|
||||
if X2 > X1 then FillBandRaw(X1, X2, H, FTheme.SpecFilter);
|
||||
|
||||
// Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
|
||||
DrawSliceFilterBands(W, H);
|
||||
|
||||
PerfAdd(pzUiSpecBands, PerfTz); // PERF: полосы фильтра (raw-заливки)
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// Градиент (заливка под кривой)
|
||||
if FFillSpectrum then
|
||||
DrawSpectrumGradient(FSpPts, W, H);
|
||||
|
||||
PerfAdd(pzUiSpecCurve, PerfTz); // PERF: += градиент под кривой
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// ═══ Фаза 2: canvas (QPainter) — линии, текст, кривая, маркеры ═══════════
|
||||
// Буква главного флага (A) на его полосе — только когда есть слайсы,
|
||||
// иначе одиночный приём не засоряем.
|
||||
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
|
||||
(FActiveVfo = 0) and Assigned(FVfoOverlay) then
|
||||
DrawBandLetter(C, X1, X2, FVfoOverlay.SliceLetter, SliceColor(FVfoOverlay.SliceLetter));
|
||||
|
||||
// Края/несущие/буквы слайсов (raw-полосы уже нарисованы в фазе 1).
|
||||
DrawSliceFilterLines(C, W, H);
|
||||
|
||||
// AGC линии. Кисть выставляем явно: TextOut подписей рисует фон кистью, и
|
||||
// до перестройки фаз здесь всегда была bsSolid/SpecFilter (от FillRect
|
||||
// полосы) — сохраняем тот же вид, не полагаясь на остаточное состояние.
|
||||
C.Brush.Color := FTheme.SpecFilter; C.Brush.Style := bsSolid;
|
||||
if FWDSPReady then
|
||||
begin
|
||||
AGCy := Round((DBmax - FAGCThresh) * InvRange * H);
|
||||
if (AGCy >= 0) and (AGCy < H) then
|
||||
begin
|
||||
C.Pen.Color := FTheme.SpecAgcColor; C.Pen.Width := 1; C.Pen.Style := psDash;
|
||||
C.MoveTo(0, AGCy); C.LineTo(W, AGCy);
|
||||
C.Pen.Style := psSolid; C.Font.Color := FTheme.SpecAgcColor; C.Font.Size := 6;
|
||||
C.TextOut(4, AGCy - 9, 'AGC T');
|
||||
end;
|
||||
AGCHangY := Round((DBmax - FAGCHangLevel) * InvRange * H);
|
||||
if (AGCHangY >= 0) and (AGCHangY < H) and (Abs(AGCHangY - AGCy) > 4) then
|
||||
begin
|
||||
C.Pen.Color := FTheme.SpecAgcHangColor; C.Pen.Width := 1; C.Pen.Style := psDot;
|
||||
C.MoveTo(0, AGCHangY); C.LineTo(W, AGCHangY);
|
||||
C.Pen.Style := psSolid; C.Font.Color := FTheme.SpecAgcHangColor; C.Font.Size := 6;
|
||||
C.TextOut(4, AGCHangY + 2, 'AGC H');
|
||||
end;
|
||||
end else begin
|
||||
AGCy := Round((DBmax - (-FAGCTop)) * InvRange * H);
|
||||
if (AGCy >= 0) and (AGCy < H) then
|
||||
begin
|
||||
C.Pen.Color := FTheme.SpecAgcColor; C.Pen.Width := 1; C.Pen.Style := psDash;
|
||||
C.MoveTo(0, AGCy); C.LineTo(W, AGCy);
|
||||
C.Pen.Style := psSolid; C.Font.Color := FTheme.SpecAgcColor; C.Font.Size := 6;
|
||||
C.TextOut(4, AGCy - 9, Format('AGC -%ddB', [FAGCTop]));
|
||||
end;
|
||||
end;
|
||||
|
||||
// Линия спектра
|
||||
C.Pen.Style := psSolid; C.Pen.Color := FTheme.SpecLine; C.Pen.Width := 1;
|
||||
C.Polyline(Slice(FSpPts, W));
|
||||
|
||||
// Края фильтра + VFO
|
||||
C.Pen.Color := FTheme.SpecFilterEdge; C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(X1, 0); C.LineTo(X1, H);
|
||||
C.MoveTo(X2, 0); C.LineTo(X2, H);
|
||||
C.Pen.Color := FTheme.SpecVfoCursor; C.Pen.Width := 2;
|
||||
C.MoveTo(VfoX, 0); C.LineTo(VfoX, H - 12);
|
||||
C.Brush.Color := FTheme.SpecVfoCursor; C.Brush.Style := bsSolid; C.Pen.Width := 1;
|
||||
C.Polygon([Point(VfoX-5,0), Point(VfoX+5,0), Point(VfoX,8)]);
|
||||
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
|
||||
begin
|
||||
C.Pen.Color := TColor($002030E0); C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(TXX1, 0); C.LineTo(TXX1, H);
|
||||
C.MoveTo(TXX2, 0); C.LineTo(TXX2, H);
|
||||
C.Pen.Width := 2;
|
||||
C.MoveTo(TXVfoX, 0); C.LineTo(TXVfoX, H - 12);
|
||||
C.Brush.Color := TColor($002030E0); C.Brush.Style := bsSolid; C.Pen.Width := 1;
|
||||
C.Polygon([Point(TXVfoX-5,0), Point(TXVfoX+5,0), Point(TXVfoX,8)]);
|
||||
end;
|
||||
|
||||
if FMarkerActive then DrawMarkerLine(C, W, H);
|
||||
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkers(C, W, H);
|
||||
DrawADCOverloadOverlay(C, W, H);
|
||||
|
||||
PerfAdd(pzUiSpecOverlays, PerfTz); // PERF: canvas-фаза (линии/текст/кривая)
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// ═══ Фаза 3: raw — композиты оверлеев + альфа-проход ═════════════════════
|
||||
// Бэндплан QO-100 — полоска внизу спектра (над ruler), под панелями оверлеев.
|
||||
if Assigned(FBandPlanOverlay) then
|
||||
FBandPlanOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||
if Assigned(FSampleRateOverlay) then
|
||||
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
|
||||
if Assigned(FVfoOverlay) then
|
||||
FVfoOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||
// Доп. слайс-флаги (B+) поверх главного.
|
||||
for i := 0 to FSliceOverlays.Count - 1 do
|
||||
TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H);
|
||||
|
||||
// Восстановление непрозрачности. На Cocoa проверено, что проход не нужен
|
||||
// (Canvas-операции сохраняют альфу $FF), поэтому там его не делаем.
|
||||
// На Qt6/win32 бэкенд другой и сглаженные края могут занулять альфу —
|
||||
// оставляем быстрый dword-OR до проверки на этих платформах.
|
||||
// ВРЕМЕННО (perf/cpu-profiling): EWSDR_ALPHA_FIX=0 пропускает проход —
|
||||
// для визуальной проверки, нужен ли он на Qt6 вообще (подозрение: нет,
|
||||
// SourceOver поверх непрозрачного фона альфу не занижает). Если артефактов
|
||||
// не будет — проход можно удалить совсем.
|
||||
RawBegin;
|
||||
try
|
||||
{$IFNDEF DARWIN}
|
||||
if not GSkipAlphaFix then
|
||||
CopyGridToSpectrum(W, H);
|
||||
PerfAdd(pzUiSpecGrid, PerfTz); // PERF: += полный memcpy сетки в битмап
|
||||
PerfTz := PerfNow; // PERF
|
||||
{$ENDIF}
|
||||
|
||||
// Полосы фильтра
|
||||
if FTXOverlay then
|
||||
begin
|
||||
if FTXVfoIndex <> FActiveVfo then
|
||||
begin
|
||||
if X2 > X1 then BlendBand(X1, X2, H, $30, $C0, $30, 100);
|
||||
if TXX2 > TXX1 then BlendBand(TXX1, TXX2, H, $E0, $30, $30, 100);
|
||||
end else
|
||||
if X2 > X1 then BlendBand(X1, X2, H, $E0, $30, $30, 100);
|
||||
end else
|
||||
if X2 > X1 then FillBandRaw(X1, X2, H, FTheme.SpecFilter);
|
||||
|
||||
// Полосы фильтра слайсов (B+) — под градиентом/кривой, как в GL-вьюхе.
|
||||
DrawSliceFilterBands(W, H);
|
||||
|
||||
PerfAdd(pzUiSpecBands, PerfTz); // PERF: полосы фильтра (raw-заливки)
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// Градиент (заливка под кривой)
|
||||
if FFillSpectrum then
|
||||
DrawSpectrumGradient(FSpPts, W, H);
|
||||
|
||||
PerfAdd(pzUiSpecCurve, PerfTz); // PERF: += градиент под кривой
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// Буква главного флага (A) на его полосе — только когда есть слайсы,
|
||||
// иначе одиночный приём не засоряем.
|
||||
if (FSliceOverlays <> nil) and (FSliceOverlays.Count > 0) and
|
||||
(FActiveVfo = 0) and Assigned(FVfoOverlay) then
|
||||
DrawBandLetterRaw(X1, X2, FVfoOverlay.SliceLetter,
|
||||
SliceColor(FVfoOverlay.SliceLetter));
|
||||
|
||||
// Края/несущие/буквы слайсов (полосы уже нарисованы выше).
|
||||
DrawSliceFilterLinesRaw(W, H);
|
||||
|
||||
// AGC линии. Подпись с фоном цвета полосы фильтра — как рисовал старый
|
||||
// canvas-TextOut с кистью bsSolid/SpecFilter.
|
||||
if FWDSPReady then
|
||||
begin
|
||||
AGCy := Round((DBmax - FAGCThresh) * InvRange * H);
|
||||
if (AGCy >= 0) and (AGCy < H) then
|
||||
begin
|
||||
RawHLine(AGCy, 0, W, FTheme.SpecAgcColor, 4, 2); // psDash
|
||||
RawText(4, AGCy - 9, 'AGC T', FTheme.SpecAgcColor, 6, False,
|
||||
FTheme.SpecFilter);
|
||||
end;
|
||||
AGCHangY := Round((DBmax - FAGCHangLevel) * InvRange * H);
|
||||
if (AGCHangY >= 0) and (AGCHangY < H) and (Abs(AGCHangY - AGCy) > 4) then
|
||||
begin
|
||||
RawHLine(AGCHangY, 0, W, FTheme.SpecAgcHangColor, 1, 2); // psDot
|
||||
RawText(4, AGCHangY + 2, 'AGC H', FTheme.SpecAgcHangColor, 6, False,
|
||||
FTheme.SpecFilter);
|
||||
end;
|
||||
end else begin
|
||||
AGCy := Round((DBmax - (-FAGCTop)) * InvRange * H);
|
||||
if (AGCy >= 0) and (AGCy < H) then
|
||||
begin
|
||||
RawHLine(AGCy, 0, W, FTheme.SpecAgcColor, 4, 2); // psDash
|
||||
RawText(4, AGCy - 9, Format('AGC -%ddB', [FAGCTop]),
|
||||
FTheme.SpecAgcColor, 6, False, FTheme.SpecFilter);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Линия спектра
|
||||
RawCurve(W, FTheme.SpecLine);
|
||||
|
||||
// Края фильтра + VFO
|
||||
RawVLine(X1, 0, H - 1, FTheme.SpecFilterEdge);
|
||||
RawVLine(X2, 0, H - 1, FTheme.SpecFilterEdge);
|
||||
RawVLine(VfoX, 0, H - 13, FTheme.SpecVfoCursor, 2);
|
||||
RawTriangleDown(VfoX, 5, 8, FTheme.SpecVfoCursor);
|
||||
if FTXOverlay and (FTXVfoIndex <> FActiveVfo) then
|
||||
begin
|
||||
RawVLine(TXX1, 0, H - 1, TColor($002030E0));
|
||||
RawVLine(TXX2, 0, H - 1, TColor($002030E0));
|
||||
RawVLine(TXVfoX, 0, H - 13, TColor($002030E0), 2);
|
||||
RawTriangleDown(TXVfoX, 5, 8, TColor($002030E0));
|
||||
end;
|
||||
|
||||
if FMarkerActive then DrawMarkerLineRaw(W, H);
|
||||
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkersRaw(W, H);
|
||||
|
||||
PerfAdd(pzUiSpecOverlays, PerfTz); // PERF: raw-линии/текст/кривая/маркеры
|
||||
PerfTz := PerfNow; // PERF
|
||||
|
||||
// Композиты оверлеев — внутри внешнего лока: их собственные BeginUpdate/
|
||||
// EndUpdate становятся вложенными (только счётчик), иначе каждая пара на
|
||||
// верхнем уровне заново дёргает handle-менеджмент битмапа (~0.3-0.4мс на
|
||||
// пару — замерено 1.8мс на кадр при пиксельной работе на ~0.15мс).
|
||||
// Spectrum-канва внутри не трогается: SampleRateOverlay меряет текст на
|
||||
// собственном скретч-битмапе (параметр C — рудимент), кэши флагов
|
||||
// рендерятся на своих битмапах.
|
||||
// Бэндплан QO-100 — полоска внизу спектра, под панелями оверлеев.
|
||||
if Assigned(FBandPlanOverlay) then
|
||||
FBandPlanOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||
if Assigned(FSampleRateOverlay) then
|
||||
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
|
||||
if Assigned(FVfoOverlay) then
|
||||
FVfoOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||
// Доп. слайс-флаги (B+) поверх главного.
|
||||
for i := 0 to FSliceOverlays.Count - 1 do
|
||||
TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H);
|
||||
|
||||
PerfAdd(pzUiComposites, PerfTz); // PERF: композиты бэндплан/панель/флаги
|
||||
PerfTz := PerfNow; // PERF
|
||||
finally
|
||||
RawEnd;
|
||||
end;
|
||||
|
||||
// ADC overload — редкий canvas-путь (DrawAlertOverlay). Ломает альфу и
|
||||
// режим доступа только в кадрах, где реально виден; после его canvas-
|
||||
// операций восстанавливаем непрозрачность (пофреймовый OR-проход для
|
||||
// обычных кадров удалён — Canvas на битмапе кадра больше не бывает).
|
||||
if FADCOverloadVisible then
|
||||
begin
|
||||
DrawADCOverloadOverlay(C, W, H);
|
||||
{$IFNDEF DARWIN}
|
||||
FSpectrumBitmap.BeginUpdate(False);
|
||||
for i := 0 to H - 1 do
|
||||
begin
|
||||
@@ -1241,9 +1579,10 @@ begin
|
||||
end;
|
||||
end;
|
||||
FSpectrumBitmap.EndUpdate(False);
|
||||
end;
|
||||
{$ENDIF}
|
||||
PerfAdd(pzUiSpecAlpha, PerfTz); // PERF: композиты флагов + альфа-OR проход
|
||||
end;
|
||||
|
||||
PerfAdd(pzUiSpecAlpha, PerfTz); // PERF: RawEnd (EndUpdate) + редкий ADC-путь
|
||||
PerfAdd(pzUiDrawSpectrum, PerfT0); // PERF: CPU-рендер кадра спектра
|
||||
end;
|
||||
|
||||
@@ -1297,7 +1636,4 @@ begin
|
||||
FSMeter.PaintSMeterRight(Sender);
|
||||
end;
|
||||
|
||||
initialization
|
||||
GSkipAlphaFix := GetEnvironmentVariable('EWSDR_ALPHA_FIX') = '0';
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user