perf: zone CPU profiler (PerfLog) + phase-restructured CPU spectrum render

Инструментация (ВРЕМЕННАЯ, не мержить в main):
- PerfLog.pas: зонные таймеры, отчёт раз в 5с в stderr (core% = доля ядра),
  EWSDR_PERF=0 выкл, EWSDR_PERF_FILE=путь — дублировать в файл
- Замеры // PERF: сеть (DDC IQ/wideband), Pluto (rx_conv/tx_resamp/tx_fill),
  DSP (push_iq/fexchange/slices/tx), аудио, UI (тики/draw/paint/gl_swap,
  разбивка draw_spectrum по фазам)
- doc/PERF_PROFILING.md: карта зон, методика, выводы

Оптимизация CPU-рендера спектра (по результатам замеров на эфире):
диагноз — дорога не пиксельная работа, а чередование raw(ScanLine)/
canvas(QPainter) доступа к битмапу на Qt6 (~6 переходов за кадр, каждый
синкает весь битмап; полоса фильтра в тысячи пикселей стоила 4x дороже
полного memcpy кадра). DrawSpectrum перестроен в фазы calc->raw->canvas->raw
(2 перехода): FillBandRaw вместо Canvas.FillRect, DrawSliceFilterMarkers
разделён на raw-полосы + canvas-линии, композиты флагов и альфа-OR в
финальной raw-фазе. Кадр 8.3мс -> 6.1мс; spec_bands 1.87мс -> 0.03мс.
Альфа-OR проход на Qt6 подтверждён нужным (без него canvas-пиксели теряют
альфу и пропадают при блите), сам цикл ~0.08мс — оставлен; тумблер
EWSDR_ALPHA_FIX=0 только для экспериментов. Кисть перед AGC-подписями
теперь ставится явно (была остаточной после перестройки фаз).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 22:07:36 +03:00
co-authored by Claude Fable 5
parent b2fa2756cf
commit 0b4d126dac
12 changed files with 692 additions and 83 deletions
+21
View File
@@ -37,6 +37,7 @@ uses
WidebandView,
StatusBar,
PlatformUtils,
PerfLog, // ВРЕМЕННО (perf/cpu-profiling): зонные CPU-таймеры
WinFirewall,
BoardUtils, WisdomBuilder, UISync,
FlatEdit, FMRepeater,
@@ -3168,7 +3169,10 @@ var
PeakTarget, MinTarget: Double;
PeakDiff, MinDiff: Double;
PeakAlpha, MinAlpha: Double;
PerfT0: Int64; // PERF
begin
PerfT0 := PerfNow; // PERF
try
if not FController.FRunning then
begin
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
@@ -3222,6 +3226,9 @@ begin
else
SetStatusText(5, 'TX idle');
end;
finally
PerfAdd(pzUiMeterTick, PerfT0); // PERF: тик метра целиком (10 Гц)
end;
end;
procedure TMainForm.SpectrumTimerTick(Sender: TObject);
@@ -3229,7 +3236,10 @@ var
NowTick: QWord;
ElapsedTick: QWord;
I: Integer;
PerfT0, PerfTz: Int64; // PERF
begin
PerfT0 := PerfNow; // PERF
try
// Синхронизируем размеры если размер панели изменился (после ресайза).
// Важно: проверяем только видимые панели — когда спектр скрыт,
// PbSpectrum.Height может быть ненулевым при FSpectrumHeight=0,
@@ -3302,13 +3312,16 @@ begin
// Обновляем оверлей если видим
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
begin
PerfTz := PerfNow; // PERF
FVfoOverlay.UpdateSMeter(FController.FLastSMeter);
FVfoOverlay.UpdateVfo(FController.FVfoA);
PushVfoOverlayExtState;
PositionVfoOverlay;
PerfAdd(pzUiVfoOverlay, PerfTz); // PERF: обновление главного VFO-флага
end;
if Length(FSliceFlags) > 0 then
begin
PerfTz := PerfNow; // PERF
PositionSliceFlags; // держим слайс-флаги на частоте
// S-метр слайсов троттлим до ~10 Гц: каждое изменение метра = софтовый
// ре-рендер флага (дорого), 60 Гц там не нужно.
@@ -3319,6 +3332,7 @@ begin
if FSliceFlags[I] <> nil then
FSliceFlags[I].UpdateSMeter(FController.SliceSMeter(FSliceFlags[I].SliceId));
end;
PerfAdd(pzUiSliceFlags, PerfTz); // PERF: позиционирование + S-метры слайс-флагов
end;
// Панорама и водопад теперь рисуются независимо по факту новых кадров.
// Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще.
@@ -3385,7 +3399,14 @@ begin
// Исходящее зеркало состояния + спектр/водопад в web — в адаптере (всё из
// FController, включая последний кадр пикселей). Сервер сам гейтит по FRunning.
if Assigned(FWebAdapter) then
begin
PerfTz := PerfNow; // PERF
FWebAdapter.PushState;
PerfAdd(pzUiWebPush, PerfTz); // PERF: web-зеркало состояния + кадры клиентам
end;
finally
PerfAdd(pzUiSpectrumTick, PerfT0); // PERF: тик спектра целиком (кадровый таймер)
end;
end;
// ===========================================================================