From 98493f08b3deb90bd22d5f0c760838b862da8082 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sun, 5 Jul 2026 16:32:35 +0300 Subject: [PATCH] =?UTF-8?q?perf(tr):=20=D1=83=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20GUI-=D1=84=D1=80=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=20~100=D0=BC=D1=81=20=D0=BD=D0=B0=20TX=E2=86=92RX=20(?= =?UTF-8?q?=D0=B6=D0=B8=D0=B2=D0=BE=D0=B9=20TX-DSP=20=D0=BF=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TX-DSP поток (TTXDSPThread) больше не пересоздаётся на каждом T/R — он создаётся один раз и живёт весь сеанс, на приём/передачу переключается флагом FTXActive (поток при False просто Continue, не трогает канал/ring). Тердаун перенесён в TWDSPEngine.Close. Раньше Terminate+WaitFor+Free на каждом отпускании PTT давал фиксированные ~100мс пустого join-оверхеда pthread_join, блокируя GUI-поток. Итог: 206→106мс, крупные фризы ушли. Остаток ~106мс = плавный WDSP fade-out TXA (SetChannelState dmode=1) — оставлен как есть (dmode=0 дал бы trailing key-click в эфир). Попутно: - MainForm: убраны прямые PbSpectrum.Invalidate из drag/wheel VFO/центра — кадр рисует таймер по FSpectrumDirty (FPS-гейт; прямой Invalidate давал GL-кадр на каждое событие мыши мимо гейта → треск в HDMI-звуке AMD APU). - AudioOutput: const-секция после {$ENDIF} блока PA-констант. Co-Authored-By: Claude Opus 4.8 --- AudioOutput.pas | 1 + MainForm.pas | 22 +++++++++------------- WDSPEngine.pas | 40 ++++++++++++++++++++++------------------ ewsdr.lpi | 5 +++-- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/AudioOutput.pas b/AudioOutput.pas index b6b9df2..da264de 100644 --- a/AudioOutput.pas +++ b/AudioOutput.pas @@ -189,6 +189,7 @@ const ); {$ENDIF} {$ENDIF} +const PA_NO_ERROR = 0; PA_FLOAT32 = LongWord(1); PA_NO_FLAG = LongWord(0); diff --git a/MainForm.pas b/MainForm.pas index cf29837..513e05f 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1155,18 +1155,16 @@ begin rfVfoA: begin FreqDispA.Frequency := Round(FController.FVfoA); - // Не рисуем синхронно из wheel/drag: частые события забивают UI-поток. - // Invalidate лишь планирует; DrawSpectrum откладываем (FSpectrumDirty). - // Линейку дёргаем сразу — её рендер дешёвый, иначе отстаёт от спектра. + // Не рисуем синхронно из wheel/drag: частые события забивают UI-поток, + // а мгновенный Invalidate давал GL-кадр на КАЖДОЕ событие мыши (при + // драге рендер упирался в vsync и обходил FPS-гейт — на AMD APU это + // трещало в HDMI-звуке того же GPU). Кадр отрисует ближайший тик + // FSpectrumTimer по FSpectrumDirty. Линейка дешёвая — её сразу. SyncSpecViewFreq; FSpectrumDirty := True; - PbSpectrum.Invalidate; if PbRuler <> nil then PbRuler.Invalidate; if FController.FLastVfoScrolled then - begin FWaterfallDirty := True; - if PbWaterfall <> nil then PbWaterfall.Invalidate; - end; end; rfActiveVfo: begin @@ -1182,11 +1180,11 @@ begin rfCenterFreq: begin // CTUN-скролл центра (drag/web): ruler-метки сдвинулись → инвалидируем - // кэш; спектр откладываем (FSpectrumDirty) — drag шлёт много событий. + // кэш; спектр откладываем (FSpectrumDirty) — drag шлёт много событий, + // кадр отрисует таймер (FPS-гейт, см. rfVfoA). FSpecView.InvalidateRulerCache; SyncSpecViewFreq; FSpectrumDirty := True; - PbSpectrum.Invalidate; if PbRuler <> nil then PbRuler.Invalidate; end; rfVfoB: @@ -1196,8 +1194,7 @@ begin begin FSpecView.InvalidateRulerCache; SyncSpecViewFreq; - FSpectrumDirty := True; - PbSpectrum.Invalidate; + FSpectrumDirty := True; // кадр — по таймеру (FPS-гейт, см. rfVfoA) if PbRuler <> nil then PbRuler.Invalidate; end; end; @@ -4568,8 +4565,7 @@ begin FController.SetSliceTarget(FSliceDragId, NewHz); PushSliceFlagState(FSliceDragId); // обновить частоту во флаге PositionSliceFlags; - FSpectrumDirty := True; - PbSpectrum.Invalidate; + FSpectrumDirty := True; // кадр — по таймеру (FPS-гейт, как при drag VFO) end; Exit; end; diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 633027c..dbaa360 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -1259,6 +1259,17 @@ begin FreeAndNil(FDSPThread); end; + // TX-DSP поток живёт весь сеанс (не пересоздаётся на T/R) — гасим его здесь, + // до закрытия WDSP-каналов (он зовёт fexchange0 на TXA_CHAN). + FTXActive := False; + if Assigned(FTXThread) then + begin + FTXThread.Terminate; + RTLEventSetEvent(FTXMicSem); + FTXThread.WaitFor; + FreeAndNil(FTXThread); + end; + // Закрываем WDSP-каналы слайсов (записи FSlices сохраняем — переоткроются в Open). FSliceLock.Enter; try @@ -2130,37 +2141,30 @@ end; procedure TWDSPEngine.SetTXRun(Run: Boolean); begin - FTXActive := Run; FDispPos := 0; FTXDispPos := 0; - if Run then - FActiveDisplayID := TX_DISP_ID - else - FActiveDisplayID := RX_DISP_ID; - if Run then begin - if FInitialized then - SetChannelState(TXA_CHAN, 1, 0); // run, no delay - // Сброс mic ring buffer и запуск TX потока + FActiveDisplayID := TX_DISP_ID; + // Сброс mic ring ДО активации, чтобы простаивавший поток не прочитал старое. FTXMicHead := 0; FTXMicTail := 0; + if FInitialized then + SetChannelState(TXA_CHAN, 1, 0); // run, no delay + // TX-DSP поток создаётся ОДИН раз и живёт весь сеанс. НЕ пересоздаём на каждом + // T/R: join (WaitFor) давал фиксированные ~100 мс фриза GUI на отпускании PTT. + // При FTXActive=False поток простаивает (см. TTXDSPThread.Execute: Continue). if not Assigned(FTXThread) then begin FTXThread := TTXDSPThread.Create(Self); FTXThread.Priority := tpHighest; end; + FTXActive := True; // активируем последним end else begin + FTXActive := False; // поток уходит в простой (не трогает канал/ring) + FActiveDisplayID := RX_DISP_ID; if FInitialized then - SetChannelState(TXA_CHAN, 0, 1); // stop, with slew - // Останавливаем TX поток - if Assigned(FTXThread) then - begin - FTXThread.Terminate; - RTLEventSetEvent(FTXMicSem); - FTXThread.WaitFor; - FreeAndNil(FTXThread); - end; + SetChannelState(TXA_CHAN, 0, 1); // stop, with slew (поток уже idle) FTXMicHead := 0; FTXMicTail := 0; end; diff --git a/ewsdr.lpi b/ewsdr.lpi index 4a338f8..3b6971b 100644 --- a/ewsdr.lpi +++ b/ewsdr.lpi @@ -17,9 +17,9 @@ - + - + @@ -83,6 +83,7 @@ +