mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Cache SampleRateOverlay to a bitmap instead of redrawing per frame
The span-selector panel was rendered directly onto the spectrum canvas every frame via DrawSelf, doing ~30 TextWidth + ~11 TextOut Canvas calls. On Cocoa these text ops are very expensive (~10.6 ms/frame, ~30% of a core) and dominated DrawSpectrum. Render the panel once into a per-pixel-alpha cache bitmap and only composite it over the spectrum each frame; rebuild only when state changes (rate, hover, hidden, bounds). The cache is built with the two-background method (render over black and white, derive straight alpha + color) so layered semi-transparent button fills and antialiased text composite over the live spectrum exactly as before. Cuts the overlay phase from ~10.6 ms to ~0.2 ms per frame; DrawSpectrum drops ~13.8 ms -> ~2.7 ms. Mirrors the caching VfoOverlay already uses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c3a8a14110
commit
2a7bf8528a
+183
-1
@@ -36,11 +36,30 @@ type
|
|||||||
FHitCount: Integer;
|
FHitCount: Integer;
|
||||||
FHotIdx: Integer;
|
FHotIdx: Integer;
|
||||||
|
|
||||||
|
// Кэш отрисовки: панель кнопок (статичный текст) рендерится в bitmap с
|
||||||
|
// попиксельной альфой и пересобирается только при смене состояния, а каждый
|
||||||
|
// кадр лишь композитится поверх спектра. Без этого ~10 мс/кадр на Cocoa
|
||||||
|
// уходило на TextOut/TextWidth (по результатам профилирования отрисовки).
|
||||||
|
FCache: TBitmap;
|
||||||
|
FCacheW: Integer;
|
||||||
|
FCacheDirty: Boolean;
|
||||||
|
FKeyRate: Integer;
|
||||||
|
FKeyHot: Integer;
|
||||||
|
FKeyHidden: Boolean;
|
||||||
|
FKeyLeft: Integer;
|
||||||
|
FKeyTop: Integer;
|
||||||
|
FKeyW: Integer;
|
||||||
|
FKeyH: Integer;
|
||||||
|
|
||||||
procedure DrawSelf(Target: TBitmap; C: TCanvas; OriginX, OriginY, W, H: Integer);
|
procedure DrawSelf(Target: TBitmap; C: TCanvas; OriginX, OriginY, W, H: Integer);
|
||||||
procedure RegisterHit(const HR: TRect; Value: Integer);
|
procedure RegisterHit(const HR: TRect; Value: Integer);
|
||||||
procedure RequestInvalidate;
|
procedure RequestInvalidate;
|
||||||
|
function ComputeContentW(C: TCanvas; AvailW: Integer): Integer;
|
||||||
|
procedure RebuildCache(C: TCanvas; W, H: Integer);
|
||||||
|
procedure CompositeCache(Target: TBitmap);
|
||||||
public
|
public
|
||||||
constructor Create(AOwner: TComponent); override;
|
constructor Create(AOwner: TComponent); override;
|
||||||
|
destructor Destroy; override;
|
||||||
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
|
||||||
procedure DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
|
procedure DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
|
||||||
procedure DrawOverlayBitmap(Target: TBitmap; W, H: Integer);
|
procedure DrawOverlayBitmap(Target: TBitmap; W, H: Integer);
|
||||||
@@ -133,6 +152,17 @@ begin
|
|||||||
FHotIdx := -1;
|
FHotIdx := -1;
|
||||||
FWidth := 320;
|
FWidth := 320;
|
||||||
FHeight := SPAN_H + PAD_Y * 2;
|
FHeight := SPAN_H + PAD_Y * 2;
|
||||||
|
FCache := TBitmap.Create;
|
||||||
|
FCache.PixelFormat := pf32bit;
|
||||||
|
FCacheW := 0;
|
||||||
|
FCacheDirty := True;
|
||||||
|
FKeyRate := -1; FKeyHot := -2; FKeyLeft := MaxInt; FKeyTop := MaxInt;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TSampleRateOverlay.Destroy;
|
||||||
|
begin
|
||||||
|
FCache.Free;
|
||||||
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSampleRateOverlay.RequestInvalidate;
|
procedure TSampleRateOverlay.RequestInvalidate;
|
||||||
@@ -148,6 +178,7 @@ begin
|
|||||||
FTop := ATop;
|
FTop := ATop;
|
||||||
FWidth := AWidth;
|
FWidth := AWidth;
|
||||||
FHeight := AHeight;
|
FHeight := AHeight;
|
||||||
|
FCacheDirty := True;
|
||||||
RequestInvalidate;
|
RequestInvalidate;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -242,11 +273,162 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TSampleRateOverlay.ComputeContentW(C: TCanvas; AvailW: Integer): Integer;
|
||||||
|
var I, CW: Integer;
|
||||||
|
begin
|
||||||
|
C.Font.Name := 'Courier New'; C.Font.Size := 7; C.Font.Style := [];
|
||||||
|
CW := PAD_X + HIDE_W + BTN_GAP;
|
||||||
|
for I := 0 to High(SPAN_NAMES) do
|
||||||
|
begin
|
||||||
|
Inc(CW, SpanBtnWidth(SPAN_NAMES[I], C));
|
||||||
|
if I < High(SPAN_NAMES) then Inc(CW, BTN_GAP);
|
||||||
|
end;
|
||||||
|
Inc(CW, PAD_X);
|
||||||
|
if CW > AvailW then CW := AvailW;
|
||||||
|
Result := CW;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Перерисовывает панель в FCache с попиксельной альфой. Метод двух фонов:
|
||||||
|
// рендерим оверлей на чёрном (B0) и белом (B1) фоне; для пикселя
|
||||||
|
// alpha = 255 - (B1 - B0), color = B0 / alpha
|
||||||
|
// что точно восстанавливает результат любого числа src-over слоёв (включая
|
||||||
|
// сглаженный текст). Затем композит FCache поверх спектра даёт тот же вид.
|
||||||
|
procedure TSampleRateOverlay.RebuildCache(C: TCanvas; W, H: Integer);
|
||||||
|
var
|
||||||
|
availW, availH, ContentW, X, Y, I, d, A: Integer;
|
||||||
|
B0, B1: TBitmap;
|
||||||
|
R0, R1, RC: PByte;
|
||||||
|
begin
|
||||||
|
availW := Min(FWidth, W - FLeft);
|
||||||
|
availH := Min(FHeight, H - FTop);
|
||||||
|
FCacheW := 0;
|
||||||
|
if (availW <= 0) or (availH <= 0) then begin FCacheDirty := False; Exit; end;
|
||||||
|
|
||||||
|
B0 := TBitmap.Create;
|
||||||
|
B1 := TBitmap.Create;
|
||||||
|
try
|
||||||
|
B0.PixelFormat := pf32bit; B0.SetSize(8, 8);
|
||||||
|
ContentW := ComputeContentW(B0.Canvas, availW);
|
||||||
|
if ContentW <= 0 then begin FCacheDirty := False; Exit; end;
|
||||||
|
|
||||||
|
B0.SetSize(ContentW, availH);
|
||||||
|
B1.PixelFormat := pf32bit; B1.SetSize(ContentW, availH);
|
||||||
|
B0.Canvas.Brush.Style := bsSolid; B0.Canvas.Brush.Color := clBlack;
|
||||||
|
B0.Canvas.FillRect(Rect(0, 0, ContentW, availH));
|
||||||
|
B1.Canvas.Brush.Style := bsSolid; B1.Canvas.Brush.Color := clWhite;
|
||||||
|
B1.Canvas.FillRect(Rect(0, 0, ContentW, availH));
|
||||||
|
|
||||||
|
DrawSelf(B0, B0.Canvas, 0, 0, ContentW, availH);
|
||||||
|
DrawSelf(B1, B1.Canvas, 0, 0, ContentW, availH); // итоговые hit-rect отсюда
|
||||||
|
for I := 0 to FHitCount - 1 do
|
||||||
|
OffsetRect(FHitRects[I], FLeft, FTop);
|
||||||
|
|
||||||
|
FCache.SetSize(ContentW, availH);
|
||||||
|
B0.BeginUpdate(False); B1.BeginUpdate(False); FCache.BeginUpdate(False);
|
||||||
|
try
|
||||||
|
for Y := 0 to availH - 1 do
|
||||||
|
begin
|
||||||
|
R0 := PByte(B0.ScanLine[Y]);
|
||||||
|
R1 := PByte(B1.ScanLine[Y]);
|
||||||
|
RC := PByte(FCache.ScanLine[Y]);
|
||||||
|
if (R0 = nil) or (R1 = nil) or (RC = nil) then Continue;
|
||||||
|
for X := 0 to ContentW - 1 do
|
||||||
|
begin
|
||||||
|
{$IFDEF DARWIN}
|
||||||
|
d := (EnsureRange(R1[1]-R0[1],0,255) + EnsureRange(R1[2]-R0[2],0,255) +
|
||||||
|
EnsureRange(R1[3]-R0[3],0,255)) div 3;
|
||||||
|
A := 255 - d;
|
||||||
|
if A <= 0 then begin RC[0]:=0; RC[1]:=0; RC[2]:=0; RC[3]:=0; end
|
||||||
|
else begin
|
||||||
|
RC[0] := A;
|
||||||
|
RC[1] := EnsureRange(R0[1]*255 div A, 0, 255);
|
||||||
|
RC[2] := EnsureRange(R0[2]*255 div A, 0, 255);
|
||||||
|
RC[3] := EnsureRange(R0[3]*255 div A, 0, 255);
|
||||||
|
end;
|
||||||
|
{$ELSE}
|
||||||
|
d := (EnsureRange(R1[0]-R0[0],0,255) + EnsureRange(R1[1]-R0[1],0,255) +
|
||||||
|
EnsureRange(R1[2]-R0[2],0,255)) div 3;
|
||||||
|
A := 255 - d;
|
||||||
|
if A <= 0 then begin RC[0]:=0; RC[1]:=0; RC[2]:=0; RC[3]:=0; end
|
||||||
|
else begin
|
||||||
|
RC[0] := EnsureRange(R0[0]*255 div A, 0, 255);
|
||||||
|
RC[1] := EnsureRange(R0[1]*255 div A, 0, 255);
|
||||||
|
RC[2] := EnsureRange(R0[2]*255 div A, 0, 255);
|
||||||
|
RC[3] := A;
|
||||||
|
end;
|
||||||
|
{$ENDIF}
|
||||||
|
Inc(R0, 4); Inc(R1, 4); Inc(RC, 4);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FCache.EndUpdate(False); B1.EndUpdate(False); B0.EndUpdate(False);
|
||||||
|
end;
|
||||||
|
FCacheW := ContentW;
|
||||||
|
finally
|
||||||
|
B0.Free; B1.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
FKeyRate := FCurrentRate; FKeyHot := FHotIdx; FKeyHidden := FPanelHidden;
|
||||||
|
FKeyLeft := FLeft; FKeyTop := FTop; FKeyW := W; FKeyH := H;
|
||||||
|
FCacheDirty := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Альфа-композит FCache поверх Target в точке (FLeft, FTop) — один проход/кадр.
|
||||||
|
procedure TSampleRateOverlay.CompositeCache(Target: TBitmap);
|
||||||
|
var X, Y, A, InvA, h: Integer; RS, RD: PByte;
|
||||||
|
begin
|
||||||
|
if (Target = nil) or (FCacheW <= 0) then Exit;
|
||||||
|
h := FCache.Height;
|
||||||
|
Target.BeginUpdate(False);
|
||||||
|
FCache.BeginUpdate(False);
|
||||||
|
try
|
||||||
|
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]);
|
||||||
|
if (RS = nil) or (RD = nil) then Continue;
|
||||||
|
Inc(RD, FLeft * 4);
|
||||||
|
for X := 0 to FCacheW - 1 do
|
||||||
|
begin
|
||||||
|
if (FLeft + X >= 0) and (FLeft + X < Target.Width) then
|
||||||
|
begin
|
||||||
|
{$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;
|
||||||
|
{$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;
|
||||||
|
{$ENDIF}
|
||||||
|
end;
|
||||||
|
Inc(RS, 4); Inc(RD, 4);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FCache.EndUpdate(False);
|
||||||
|
Target.EndUpdate(False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSampleRateOverlay.DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
|
procedure TSampleRateOverlay.DrawOverlay(Target: TBitmap; C: TCanvas; W, H: Integer);
|
||||||
begin
|
begin
|
||||||
if (FWidth <= 0) or (FHeight <= 0) then Exit;
|
if (FWidth <= 0) or (FHeight <= 0) then Exit;
|
||||||
if (FLeft >= W) or (FTop >= H) then Exit;
|
if (FLeft >= W) or (FTop >= H) then Exit;
|
||||||
DrawSelf(Target, C, FLeft, FTop, Min(FWidth, W - FLeft), Min(FHeight, H - FTop));
|
if FCacheDirty or (FKeyRate <> FCurrentRate) or (FKeyHot <> FHotIdx) or
|
||||||
|
(FKeyHidden <> FPanelHidden) or (FKeyLeft <> FLeft) or (FKeyTop <> FTop) or
|
||||||
|
(FKeyW <> W) or (FKeyH <> H) then
|
||||||
|
RebuildCache(C, W, H);
|
||||||
|
CompositeCache(Target);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSampleRateOverlay.DrawOverlayBitmap(Target: TBitmap; W, H: Integer);
|
procedure TSampleRateOverlay.DrawOverlayBitmap(Target: TBitmap; W, H: Integer);
|
||||||
|
|||||||
Reference in New Issue
Block a user