feat(display): скорость водопада с max-hold накоплением + MSAA спектра

- настройка Speed (wf_speed_div 1/2/4/8, Waterfall→Rendering): замедление
  накапливает кадры max-hold'ом в SetWaterfallData — короткие посылки (CW)
  не выпадают из строки; двойная децимация MainForm убрана
  (EffectiveWfInterval, CPU-путь не быстрее полукадровой)
- настройка Anti-aliasing (spec_msaa 1/2/4/8, дефолт 4x, Spectrum→Rendering):
  MultiSampling на GL-контроле спектра; смена в рантайме пересоздаёт
  контекст → ResetGLCache обнуляет текстурные кэши без glDelete

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 16:48:55 +03:00
co-authored by Claude Fable 5
parent 092a202f5f
commit e260995218
6 changed files with 164 additions and 35 deletions
+44 -28
View File
@@ -163,9 +163,6 @@ type
// переехало в TRadioController (нужно headless-load в DoConnectDevice).
// Снимок последнего кадра спектра/водопада для web переехал в TRadioController
// (FSpectrumBuf/FWaterfallBuf) — нужен headless-демону.
// Waterfall frame timing
FWaterfallFrameInterval: Integer;
FWaterfallFrameCounter: Integer;
FAgcLineCounter: Integer;
// ---- Splitter ----
FSplitterDrag: Boolean;
@@ -588,7 +585,9 @@ type
procedure ApplyWaterfallParams(WfDet, WfAvgMode: Integer;
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double);
procedure ApplyWfRenderParams(Palette: Integer; Gamma: Double;
ResLimit: Integer);
ResLimit, SpeedDiv: Integer);
procedure ApplySpecMSAA(Samples: Integer);
function EffectiveWfInterval: Integer;
procedure ApplyWfAGCNF(WfAGC, WfNF: Boolean);
procedure ApplyADCSettings(Dither, Random: Boolean);
procedure ApplyWebSettings(Enabled: Boolean; Port: Integer;
@@ -845,8 +844,6 @@ begin
FController.FFMRptOffsetHz := RPT_DEFAULT_2M;
FController.FFMRptAutoActive := False;
FController.FFMCTCSSAutoActive := False;
FWaterfallFrameInterval := 2;
FWaterfallFrameCounter := 0;
FController.FDevConnected := False;
// Spectrum grid settings defaults
FController.FSpecRefLevel := -20.0;
@@ -1010,8 +1007,6 @@ begin
// OpenGLContextEx (QOpenGLWidget-бэкенд) работает и в нативном Wayland,
// старая X11-заглушка больше не нужна
FUseOpenGLSpectrum := HasOpenGLSpectrumSwitch;
if FUseOpenGLSpectrum then
FWaterfallFrameInterval := 1;
if FUseOpenGLSpectrum then
FSpecView := TSpectrumViewOpenGL.Create
else
@@ -1037,7 +1032,7 @@ begin
FSpecView.SpecGridStep := FController.FSpecGridStep;
// TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k)
FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE;
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
FSpecView.WfFrameInterval := EffectiveWfInterval;
FSpecView.PAMaxPower := FController.FPAMaxPower;
FWidebandView := TWidebandView.Create;
@@ -1475,6 +1470,8 @@ begin
FSpecView.WfAGCOffset := FController.FWfAGCOffset;
FSpecView.WfPalette := FController.FWfPalette;
FSpecView.WfGamma := FController.FWfGamma;
FSpecView.WfFrameInterval := EffectiveWfInterval;
ApplySpecMSAA(FController.FSpecMSAA);
FSpecView.ResetWfAvgBuf;
if Assigned(FWidebandView) then
FWidebandView.SetFillSpectrum(FController.FWidebandFill);
@@ -2211,6 +2208,7 @@ begin
PbSpectrum := TOpenGLControl.Create(Self);
TOpenGLControl(PbSpectrum).AutoResizeViewport := False;
TOpenGLControl(PbSpectrum).DoubleBuffered := True;
TOpenGLControl(PbSpectrum).MultiSampling := Max(1, FController.FSpecMSAA);
TOpenGLControl(PbSpectrum).OnPaint := FSpecView.PaintSpectrum;
TOpenGLControl(PbSpectrum).OnMouseDown := PbSpectrumMouseDown;
TOpenGLControl(PbSpectrum).OnDblClick := PbSpectrumDblClick;
@@ -6400,16 +6398,12 @@ end;
procedure TMainForm.OnWaterfallReady(const Pixels: array of Single;
Count: Integer);
// Подписчик на FController.OnWaterfallData — рендер с децимацией скорости
// прокрутки (FWaterfallFrameInterval). Web получает каждый кадр (в контроллере).
// Подписчик на FController.OnWaterfallData. Каждый кадр уходит во view —
// замедление/накопление (max-hold) делает сам TWaterfallView по
// WfFrameInterval; рендер дёргаем только когда view собрал строку.
begin
Inc(FWaterfallFrameCounter);
if FWaterfallFrameCounter >= Max(1, FWaterfallFrameInterval) then
begin
FWaterfallFrameCounter := 0;
FSpecView.SetWaterfallData(Pixels, Count);
FWaterfallDirty := True;
end;
FSpecView.SetWaterfallData(Pixels, Count);
if FSpecView.WaterfallDirty then FWaterfallDirty := True;
end;
// ===========================================================================
@@ -6442,19 +6436,37 @@ begin
end;
procedure TMainForm.ApplyWfRenderParams(Palette: Integer; Gamma: Double;
ResLimit: Integer);
ResLimit, SpeedDiv: Integer);
begin
FController.FWfPalette := Palette;
FController.FWfGamma := Gamma;
FController.FDisplayPixels := ResLimit;
FController.FWfSpeedDiv := SpeedDiv;
FSpecView.WfPalette := Palette;
FSpecView.WfGamma := Gamma;
FSpecView.WfFrameInterval := EffectiveWfInterval;
if FController.FWDSPReady then
FController.FDSPEngine.SetDisplayResLimit(ResLimit);
FWaterfallDirty := True;
FSpectrumDirty := True;
end;
procedure TMainForm.ApplySpecMSAA(Samples: Integer);
begin
Samples := EnsureRange(Samples, 1, 8);
FController.FSpecMSAA := Samples;
if FUseOpenGLSpectrum and (PbSpectrum is TOpenGLControl) and
(TOpenGLControl(PbSpectrum).MultiSampling <> Cardinal(Samples)) then
begin
// Смена MSAA пересоздаёт хэндл → старый GL-контекст (и все текстуры)
// умирает; кэши view нужно обнулить БЕЗ glDelete.
TOpenGLControl(PbSpectrum).MultiSampling := Samples;
if FSpecView is TSpectrumViewOpenGL then
TSpectrumViewOpenGL(FSpecView).ResetGLCache;
FSpectrumDirty := True;
end;
end;
procedure TMainForm.ApplyGridParams(RefLevel, Range, GridStep: Double);
begin
FController.FSpecRefLevel := RefLevel;
@@ -6565,19 +6577,21 @@ begin
FDisplayFPS := FPS;
FController.FLoadedGlobal.DisplayFPS := FPS; // зеркало в persist-буфер контроллера
FSpectrumTimer.Interval := 1000 div FPS;
// CPU path keeps the historical every-other-frame waterfall update.
// OpenGL uploads only one texture row per frame, so it can match display FPS.
if FUseOpenGLSpectrum then
FWaterfallFrameInterval := 1
else
FWaterfallFrameInterval := 2;
FWaterfallFrameCounter := 0;
FWaterfallDirty := True;
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
FSpecView.WfFrameInterval := EffectiveWfInterval;
if FController.FWDSPReady then
FController.FDSPEngine.SetDisplayFPS(FPS);
end;
function TMainForm.EffectiveWfInterval: Integer;
// Делитель скорости водопада: пользовательский Speed (max-hold накопление
// в TWaterfallView); CPU-путь дополнительно не быстрее полукадровой частоты
// (исторически — экономия на полной перерисовке bitmap).
begin
Result := Max(1, FController.FWfSpeedDiv);
if not FUseOpenGLSpectrum then Result := Max(Result, 2);
end;
procedure TMainForm.ApplyFreqMhzDigits(Digits: Integer);
begin
Digits := EnsureRange(Digits, 3, 5);
@@ -6623,6 +6637,7 @@ begin
SF.OnDisplayChange := ApplyDisplayParams;
SF.OnWaterfallChange := ApplyWaterfallParams;
SF.OnWfRenderChange := ApplyWfRenderParams;
SF.OnSpecMSAAChange := ApplySpecMSAA;
SF.OnGridChange := ApplyGridParams;
SF.OnAudioDevChange := ApplyAudioDevice;
SF.OnAudioInDevChange := ApplyAudioInputDevice;
@@ -6668,7 +6683,8 @@ begin
FController.FSpectrumFill);
SF.LoadWfAGCNF(FController.FWfAGCEnabled, FController.FWfNFEnabled);
SF.LoadWfRender(FController.FWfPalette, FController.FWfGamma,
FController.FDisplayPixels);
FController.FDisplayPixels, FController.FWfSpeedDiv);
SF.LoadSpecMSAA(FController.FSpecMSAA);
SF.LoadADCSettings(FController.FDitherEnabled, FController.FRandomEnabled);
SF.LoadWebSettings(FWebEnabled, FWebPort, FWebBindAddr, FWebUser, FWebPass);
SF.LoadFPS(FDisplayFPS);