mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Add OpenGL waterfall renderer
This commit is contained in:
+30
-9
@@ -431,7 +431,7 @@ type
|
|||||||
PbSpectrum: TControl;
|
PbSpectrum: TControl;
|
||||||
PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом
|
PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом
|
||||||
PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад
|
PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад
|
||||||
PbWaterfall: TPaintBox;
|
PbWaterfall: TControl;
|
||||||
|
|
||||||
// ---- Status bar ----
|
// ---- Status bar ----
|
||||||
StatusPanel: TMainStatusBar;
|
StatusPanel: TMainStatusBar;
|
||||||
@@ -1469,6 +1469,8 @@ begin
|
|||||||
FSplitterDrag := False;
|
FSplitterDrag := False;
|
||||||
|
|
||||||
FUseOpenGLSpectrum := HasOpenGLSpectrumSwitch;
|
FUseOpenGLSpectrum := HasOpenGLSpectrumSwitch;
|
||||||
|
if FUseOpenGLSpectrum then
|
||||||
|
FWaterfallFrameInterval := 1;
|
||||||
if FUseOpenGLSpectrum then
|
if FUseOpenGLSpectrum then
|
||||||
FSpecView := TSpectrumViewOpenGL.Create
|
FSpecView := TSpectrumViewOpenGL.Create
|
||||||
else
|
else
|
||||||
@@ -2186,12 +2188,26 @@ begin
|
|||||||
PanelSplitter.OnMouseMove := SplitterMouseMove;
|
PanelSplitter.OnMouseMove := SplitterMouseMove;
|
||||||
PanelSplitter.OnMouseUp := SplitterMouseUp;
|
PanelSplitter.OnMouseUp := SplitterMouseUp;
|
||||||
|
|
||||||
PbWaterfall := TPaintBox.Create(Self);
|
if FUseOpenGLSpectrum then
|
||||||
|
begin
|
||||||
|
PbWaterfall := TOpenGLControl.Create(Self);
|
||||||
|
TOpenGLControl(PbWaterfall).AutoResizeViewport := False;
|
||||||
|
TOpenGLControl(PbWaterfall).DoubleBuffered := True;
|
||||||
|
TOpenGLControl(PbWaterfall).OnPaint := FSpecView.PaintWaterfall;
|
||||||
|
TOpenGLControl(PbWaterfall).OnMouseDown := PbWaterfallMouseDown;
|
||||||
|
TOpenGLControl(PbWaterfall).OnMouseMove := PbWaterfallMouseMove;
|
||||||
|
TOpenGLControl(PbWaterfall).OnMouseUp := PbWaterfallMouseUp;
|
||||||
|
TSpectrumViewOpenGL(FSpecView).AttachWaterfallControl(TOpenGLControl(PbWaterfall));
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
PbWaterfall := TPaintBox.Create(Self);
|
||||||
|
TPaintBox(PbWaterfall).OnPaint := FSpecView.PaintWaterfall;
|
||||||
|
TPaintBox(PbWaterfall).OnMouseDown := PbWaterfallMouseDown;
|
||||||
|
TPaintBox(PbWaterfall).OnMouseMove := PbWaterfallMouseMove;
|
||||||
|
TPaintBox(PbWaterfall).OnMouseUp := PbWaterfallMouseUp;
|
||||||
|
end;
|
||||||
PbWaterfall.Parent := PanelRight;
|
PbWaterfall.Parent := PanelRight;
|
||||||
PbWaterfall.OnPaint := FSpecView.PaintWaterfall;
|
|
||||||
PbWaterfall.OnMouseDown := PbWaterfallMouseDown;
|
|
||||||
PbWaterfall.OnMouseMove := PbWaterfallMouseMove;
|
|
||||||
PbWaterfall.OnMouseUp := PbWaterfallMouseUp;
|
|
||||||
|
|
||||||
// Initial layout
|
// Initial layout
|
||||||
ResizeSpectrumPanels;
|
ResizeSpectrumPanels;
|
||||||
@@ -2616,7 +2632,8 @@ begin
|
|||||||
|
|
||||||
if PbSpectrum is TPaintBox then
|
if PbSpectrum is TPaintBox then
|
||||||
TPaintBox(PbSpectrum).Color := T.BG;
|
TPaintBox(PbSpectrum).Color := T.BG;
|
||||||
PbWaterfall.Color := T.BG;
|
if PbWaterfall is TPaintBox then
|
||||||
|
TPaintBox(PbWaterfall).Color := T.BG;
|
||||||
if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel;
|
if PanelSMeterRight <> nil then PanelSMeterRight.Color := T.Panel;
|
||||||
if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel;
|
if PbSMeterRight <> nil then PbSMeterRight.Color := T.Panel;
|
||||||
ApplyStatusTheme(T);
|
ApplyStatusTheme(T);
|
||||||
@@ -7025,8 +7042,12 @@ begin
|
|||||||
if FPS > 100 then FPS := 100;
|
if FPS > 100 then FPS := 100;
|
||||||
FDisplayFPS := FPS;
|
FDisplayFPS := FPS;
|
||||||
FSpectrumTimer.Interval := 1000 div FPS;
|
FSpectrumTimer.Interval := 1000 div FPS;
|
||||||
// Thetis по умолчанию обновляет водопад через один display frame.
|
// CPU path keeps the historical every-other-frame waterfall update.
|
||||||
FWaterfallFrameInterval := 2;
|
// OpenGL uploads only one texture row per frame, so it can match display FPS.
|
||||||
|
if FUseOpenGLSpectrum then
|
||||||
|
FWaterfallFrameInterval := 1
|
||||||
|
else
|
||||||
|
FWaterfallFrameInterval := 2;
|
||||||
FWaterfallFrameCounter := 0;
|
FWaterfallFrameCounter := 0;
|
||||||
FWaterfallDirty := True;
|
FWaterfallDirty := True;
|
||||||
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
|
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
|
||||||
|
|||||||
+3
-3
@@ -117,7 +117,7 @@ type
|
|||||||
procedure SetWfAGCOffset(V: Double);
|
procedure SetWfAGCOffset(V: Double);
|
||||||
function GetWfFrameInterval: Integer;
|
function GetWfFrameInterval: Integer;
|
||||||
procedure SetWfFrameInterval(V: Integer);
|
procedure SetWfFrameInterval(V: Integer);
|
||||||
procedure SetPbWaterfall(V: TPaintBox);
|
procedure SetPbWaterfall(V: TControl);
|
||||||
// SMeter sub-view delegating accessors
|
// SMeter sub-view delegating accessors
|
||||||
function GetLastSMeter: Double;
|
function GetLastSMeter: Double;
|
||||||
procedure SetLastSMeter(V: Double);
|
procedure SetLastSMeter(V: Double);
|
||||||
@@ -195,7 +195,7 @@ type
|
|||||||
property WfFrameInterval: Integer read GetWfFrameInterval write SetWfFrameInterval;
|
property WfFrameInterval: Integer read GetWfFrameInterval write SetWfFrameInterval;
|
||||||
|
|
||||||
// ── Ссылки на PaintBox ────────────────────────────────────────────────────
|
// ── Ссылки на PaintBox ────────────────────────────────────────────────────
|
||||||
property PbWaterfall: TPaintBox write SetPbWaterfall;
|
property PbWaterfall: TControl write SetPbWaterfall;
|
||||||
property PbRuler: TPaintBox write SetPbRuler;
|
property PbRuler: TPaintBox write SetPbRuler;
|
||||||
property PbSMeterRight: TPaintBox write SetPbSMeterRight;
|
property PbSMeterRight: TPaintBox write SetPbSMeterRight;
|
||||||
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
|
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
|
||||||
@@ -410,7 +410,7 @@ function TSpectrumView.GetWfAGCOffset: Double; begin Result := FWaterfall.
|
|||||||
procedure TSpectrumView.SetWfAGCOffset(V: Double); begin FWaterfall.WfAGCOffset := V; end;
|
procedure TSpectrumView.SetWfAGCOffset(V: Double); begin FWaterfall.WfAGCOffset := V; end;
|
||||||
function TSpectrumView.GetWfFrameInterval: Integer; begin Result := FWaterfall.WfFrameInterval; end;
|
function TSpectrumView.GetWfFrameInterval: Integer; begin Result := FWaterfall.WfFrameInterval; end;
|
||||||
procedure TSpectrumView.SetWfFrameInterval(V: Integer); begin FWaterfall.WfFrameInterval := V; end;
|
procedure TSpectrumView.SetWfFrameInterval(V: Integer); begin FWaterfall.WfFrameInterval := V; end;
|
||||||
procedure TSpectrumView.SetPbWaterfall(V: TPaintBox); begin FWaterfall.PbWaterfall := V; end;
|
procedure TSpectrumView.SetPbWaterfall(V: TControl); begin FWaterfall.PbWaterfall := V; end;
|
||||||
|
|
||||||
function TSpectrumView.GetLastSMeter: Double; begin Result := FSMeter.LastSMeter; end;
|
function TSpectrumView.GetLastSMeter: Double; begin Result := FSMeter.LastSMeter; end;
|
||||||
procedure TSpectrumView.SetLastSMeter(V: Double); begin FSMeter.LastSMeter := V; end;
|
procedure TSpectrumView.SetLastSMeter(V: Double); begin FSMeter.LastSMeter := V; end;
|
||||||
|
|||||||
+11
-1
@@ -17,7 +17,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, Controls, Math, Types,
|
Classes, SysUtils, Graphics, Controls, Math, Types,
|
||||||
OpenGLContext, GL,
|
OpenGLContext, GL,
|
||||||
AppTheme, SpectrumView;
|
AppTheme, SpectrumView, WaterfallViewOpengl;
|
||||||
|
|
||||||
type
|
type
|
||||||
TGLTextureCache = record
|
TGLTextureCache = record
|
||||||
@@ -83,6 +83,7 @@ type
|
|||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure AttachControl(C: TOpenGLControl);
|
procedure AttachControl(C: TOpenGLControl);
|
||||||
|
procedure AttachWaterfallControl(C: TOpenGLControl);
|
||||||
procedure SetSpectrumBitmapSize(W, H: Integer); override;
|
procedure SetSpectrumBitmapSize(W, H: Integer); override;
|
||||||
function SpectrumBitmapWidth: Integer; override;
|
function SpectrumBitmapWidth: Integer; override;
|
||||||
procedure DrawSpectrum; override;
|
procedure DrawSpectrum; override;
|
||||||
@@ -114,6 +115,9 @@ end;
|
|||||||
constructor TSpectrumViewOpenGL.Create;
|
constructor TSpectrumViewOpenGL.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
|
FWaterfall.Free;
|
||||||
|
FWaterfall := TWaterfallViewOpenGL.Create;
|
||||||
|
FWaterfall.WfFrameInterval := 1;
|
||||||
FGridDirty := True;
|
FGridDirty := True;
|
||||||
FOverlayDirty := True;
|
FOverlayDirty := True;
|
||||||
FSampleOverlayDirty := True;
|
FSampleOverlayDirty := True;
|
||||||
@@ -144,6 +148,12 @@ begin
|
|||||||
FGLControl := C;
|
FGLControl := C;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSpectrumViewOpenGL.AttachWaterfallControl(C: TOpenGLControl);
|
||||||
|
begin
|
||||||
|
if FWaterfall is TWaterfallViewOpenGL then
|
||||||
|
TWaterfallViewOpenGL(FWaterfall).AttachControl(C);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSpectrumViewOpenGL.DeleteTexture(var T: TGLTextureCache);
|
procedure TSpectrumViewOpenGL.DeleteTexture(var T: TGLTextureCache);
|
||||||
begin
|
begin
|
||||||
if T.Tex <> 0 then
|
if T.Tex <> 0 then
|
||||||
|
|||||||
+10
-10
@@ -17,7 +17,7 @@ uses
|
|||||||
|
|
||||||
type
|
type
|
||||||
TWaterfallView = class
|
TWaterfallView = class
|
||||||
private
|
protected
|
||||||
FWaterfallBitmap: TBitmap;
|
FWaterfallBitmap: TBitmap;
|
||||||
FWfBitmap: TBitmap;
|
FWfBitmap: TBitmap;
|
||||||
FWfBitmapW: Integer;
|
FWfBitmapW: Integer;
|
||||||
@@ -45,7 +45,7 @@ type
|
|||||||
FMarkerX: Integer;
|
FMarkerX: Integer;
|
||||||
FTheme: TAppTheme;
|
FTheme: TAppTheme;
|
||||||
FLightTheme: Boolean;
|
FLightTheme: Boolean;
|
||||||
FPbWaterfall: TPaintBox;
|
FPbWaterfall: TControl;
|
||||||
|
|
||||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||||
public
|
public
|
||||||
@@ -66,15 +66,15 @@ type
|
|||||||
property WfFrameInterval: Integer read FWfFrameInterval write FWfFrameInterval;
|
property WfFrameInterval: Integer read FWfFrameInterval write FWfFrameInterval;
|
||||||
property MarkerActive: Boolean read FMarkerActive write FMarkerActive;
|
property MarkerActive: Boolean read FMarkerActive write FMarkerActive;
|
||||||
property MarkerX: Integer read FMarkerX write FMarkerX;
|
property MarkerX: Integer read FMarkerX write FMarkerX;
|
||||||
property PbWaterfall: TPaintBox write FPbWaterfall;
|
property PbWaterfall: TControl write FPbWaterfall;
|
||||||
|
|
||||||
procedure SetWaterfallData(const Pixels: array of Single; Count: Integer);
|
procedure SetWaterfallData(const Pixels: array of Single; Count: Integer); virtual;
|
||||||
procedure DrawWaterfall;
|
procedure DrawWaterfall; virtual;
|
||||||
procedure PaintWaterfall(Sender: TObject);
|
procedure PaintWaterfall(Sender: TObject); virtual;
|
||||||
procedure SetWaterfallBitmapSize(W, H: Integer);
|
procedure SetWaterfallBitmapSize(W, H: Integer); virtual;
|
||||||
procedure ResetWfAvgBuf;
|
procedure ResetWfAvgBuf; virtual;
|
||||||
procedure ResetWfBuf;
|
procedure ResetWfBuf; virtual;
|
||||||
procedure SetTheme(const T: TAppTheme);
|
procedure SetTheme(const T: TAppTheme); virtual;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|||||||
@@ -0,0 +1,520 @@
|
|||||||
|
unit WaterfallViewOpengl;
|
||||||
|
|
||||||
|
{
|
||||||
|
OpenGL renderer for the waterfall pane.
|
||||||
|
|
||||||
|
The CPU path rebuilds a full bitmap after scrolling rows in memory. This
|
||||||
|
renderer keeps one GL texture and uploads only the newest waterfall row.
|
||||||
|
}
|
||||||
|
|
||||||
|
{$IFDEF FPC}
|
||||||
|
{$MODE Delphi}
|
||||||
|
{$ENDIF}
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Classes, SysUtils, Graphics, Controls, Math,
|
||||||
|
OpenGLContext, GL,
|
||||||
|
AppTheme, WaterfallView;
|
||||||
|
|
||||||
|
type
|
||||||
|
TWaterfallViewOpenGL = class(TWaterfallView)
|
||||||
|
private
|
||||||
|
FGLControl: TOpenGLControl;
|
||||||
|
FTexture: GLuint;
|
||||||
|
FTexW: Integer;
|
||||||
|
FTexH: Integer;
|
||||||
|
FLatestRow: Integer;
|
||||||
|
FRowPixels: array of Byte;
|
||||||
|
FMarkerTex: GLuint;
|
||||||
|
FMarkerTexW: Integer;
|
||||||
|
FMarkerTexH: Integer;
|
||||||
|
FMarkerText: string;
|
||||||
|
|
||||||
|
procedure DeleteTexture(var Tex: GLuint);
|
||||||
|
procedure EnsureTexture(W, H: Integer);
|
||||||
|
procedure UploadCurrentRow(W: Integer);
|
||||||
|
procedure DrawTextureWrapped(W, H: Integer);
|
||||||
|
procedure DrawMarker(W, H: Integer);
|
||||||
|
procedure UploadMarkerText(const S: string);
|
||||||
|
procedure UpdateLevels;
|
||||||
|
function FormatFreqGL(Hz: Double): string;
|
||||||
|
function ColorForDB(ValueDB, LowDB, HighDB: Double): LongWord;
|
||||||
|
public
|
||||||
|
constructor Create;
|
||||||
|
destructor Destroy; override;
|
||||||
|
procedure AttachControl(C: TOpenGLControl);
|
||||||
|
procedure SetWaterfallBitmapSize(W, H: Integer); override;
|
||||||
|
procedure DrawWaterfall; override;
|
||||||
|
procedure PaintWaterfall(Sender: TObject); override;
|
||||||
|
procedure SetTheme(const T: TAppTheme); override;
|
||||||
|
procedure ResetWfBuf; override;
|
||||||
|
procedure ResetWfAvgBuf; override;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
function EnsureByte(V: Integer): Byte;
|
||||||
|
begin
|
||||||
|
if V < 0 then Result := 0
|
||||||
|
else if V > 255 then Result := 255
|
||||||
|
else Result := V;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TWaterfallViewOpenGL.FormatFreqGL(Hz: Double): string;
|
||||||
|
var Mhz: Int64; KHz, Rest: Integer;
|
||||||
|
begin
|
||||||
|
Mhz := Trunc(Hz / 1000000);
|
||||||
|
KHz := Trunc((Hz - Mhz * 1000000) / 1000);
|
||||||
|
Rest := Trunc(Hz) mod 1000;
|
||||||
|
Result := Format('%d.%3.3d.%3.3d', [Mhz, KHz, Rest]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TWaterfallViewOpenGL.Create;
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FTexture := 0;
|
||||||
|
FMarkerTex := 0;
|
||||||
|
FTexW := 0;
|
||||||
|
FTexH := 0;
|
||||||
|
FLatestRow := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TWaterfallViewOpenGL.Destroy;
|
||||||
|
begin
|
||||||
|
if Assigned(FGLControl) and FGLControl.MakeCurrent then
|
||||||
|
begin
|
||||||
|
DeleteTexture(FTexture);
|
||||||
|
DeleteTexture(FMarkerTex);
|
||||||
|
end;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.AttachControl(C: TOpenGLControl);
|
||||||
|
begin
|
||||||
|
FGLControl := C;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.DeleteTexture(var Tex: GLuint);
|
||||||
|
begin
|
||||||
|
if Tex <> 0 then glDeleteTextures(1, @Tex);
|
||||||
|
Tex := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TWaterfallViewOpenGL.ColorForDB(ValueDB, LowDB, HighDB: Double): LongWord;
|
||||||
|
const
|
||||||
|
NSTOPS = 8;
|
||||||
|
SR: array[0..NSTOPS-1] of Integer = (224, 160, 20, 0, 0, 220, 255, 255);
|
||||||
|
SG: array[0..NSTOPS-1] of Integer = (230, 185, 80, 185, 200, 210, 90, 20);
|
||||||
|
SB: array[0..NSTOPS-1] of Integer = (232, 210, 200, 210, 80, 0, 0, 20);
|
||||||
|
var
|
||||||
|
Overall, Local, T: Double;
|
||||||
|
Seg, R, G, B: Integer;
|
||||||
|
begin
|
||||||
|
if FLightTheme then
|
||||||
|
begin
|
||||||
|
if ValueDB <= LowDB then begin Result := $FFE0E6E8; Exit; end;
|
||||||
|
if ValueDB >= HighDB then
|
||||||
|
begin
|
||||||
|
Result := ($FF shl 24) or (SR[NSTOPS-1] shl 16) or
|
||||||
|
(SG[NSTOPS-1] shl 8) or SB[NSTOPS-1];
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
T := (ValueDB - LowDB) / Max(1E-9, HighDB - LowDB) * (NSTOPS - 1);
|
||||||
|
Seg := Min(NSTOPS - 2, Trunc(T));
|
||||||
|
T := T - Seg;
|
||||||
|
R := EnsureRange(Round(SR[Seg] * (1.0 - T) + SR[Seg+1] * T), 0, 255);
|
||||||
|
G := EnsureRange(Round(SG[Seg] * (1.0 - T) + SG[Seg+1] * T), 0, 255);
|
||||||
|
B := EnsureRange(Round(SB[Seg] * (1.0 - T) + SB[Seg+1] * T), 0, 255);
|
||||||
|
Result := ($FF shl 24) or (R shl 16) or (G shl 8) or B;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if ValueDB <= LowDB then begin Result := $FF000000; Exit; end;
|
||||||
|
if ValueDB >= HighDB then
|
||||||
|
begin
|
||||||
|
Result := ($FF shl 24) or (255 shl 16) or (124 shl 8) or 192;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
Overall := (ValueDB - LowDB) / Max(1E-9, HighDB - LowDB);
|
||||||
|
if Overall < (2.0 / 9.0) then
|
||||||
|
begin Local := Overall / (2.0/9.0); R := 0; G := 0; B := Round(Local * 255.0); end
|
||||||
|
else if Overall < (3.0 / 9.0) then
|
||||||
|
begin Local := (Overall - 2.0/9.0) / (1.0/9.0); R := 0; G := Round(Local*255.0); B := 255; end
|
||||||
|
else if Overall < (4.0 / 9.0) then
|
||||||
|
begin Local := (Overall - 3.0/9.0) / (1.0/9.0); R := 0; G := 255; B := Round((1.0-Local)*255.0); end
|
||||||
|
else if Overall < (5.0 / 9.0) then
|
||||||
|
begin Local := (Overall - 4.0/9.0) / (1.0/9.0); R := Round(Local*255.0); G := 255; B := 0; end
|
||||||
|
else if Overall < (7.0 / 9.0) then
|
||||||
|
begin Local := (Overall - 5.0/9.0) / (2.0/9.0); R := 255; G := Round((1.0-Local)*255.0); B := 0; end
|
||||||
|
else if Overall < (8.0 / 9.0) then
|
||||||
|
begin Local := (Overall - 7.0/9.0) / (1.0/9.0); R := 255; G := 0; B := Round(Local*255.0); end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Local := (Overall - 8.0/9.0) / (1.0/9.0);
|
||||||
|
R := Round((0.75 + 0.25 * (1.0 - Local)) * 255.0);
|
||||||
|
G := Round(Local * 255.0 * 0.5);
|
||||||
|
B := 255;
|
||||||
|
end;
|
||||||
|
Result := ($FF shl 24) or (EnsureRange(R, 0, 255) shl 16) or
|
||||||
|
(EnsureRange(G, 0, 255) shl 8) or EnsureRange(B, 0, 255);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.UpdateLevels;
|
||||||
|
var
|
||||||
|
X, SrcCount, LowTargetCount, HighTargetCount, CumCount, HistIdx: Integer;
|
||||||
|
Hist: array[0..191] of Integer;
|
||||||
|
HistMinDB, HistMaxDB, HistStepDB, NoiseFloorDB, SignalTopDB: Double;
|
||||||
|
TargetLow, TargetHigh: Double;
|
||||||
|
const
|
||||||
|
ALPHA_HIGH = 0.10;
|
||||||
|
ALPHA_LOW = 0.08;
|
||||||
|
WF_AUTO_OFFSET = -4.0;
|
||||||
|
WF_MIN_RANGE = 48.0;
|
||||||
|
WF_MAX_RANGE = 62.0;
|
||||||
|
begin
|
||||||
|
SrcCount := EnsureRange(FWaterfallBufCount, 2, 1024);
|
||||||
|
FillChar(Hist, SizeOf(Hist), 0);
|
||||||
|
HistMinDB := -170.0;
|
||||||
|
HistMaxDB := 22.0;
|
||||||
|
HistStepDB := (HistMaxDB - HistMinDB) / Length(Hist);
|
||||||
|
for X := 0 to SrcCount - 1 do
|
||||||
|
begin
|
||||||
|
HistIdx := EnsureRange(Trunc((FWaterfallBuf[X] - HistMinDB) / HistStepDB), 0, High(Hist));
|
||||||
|
Inc(Hist[HistIdx]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
LowTargetCount := Round(SrcCount * 0.30);
|
||||||
|
HighTargetCount := Round(SrcCount * 0.98);
|
||||||
|
CumCount := 0;
|
||||||
|
NoiseFloorDB := FWfLow;
|
||||||
|
SignalTopDB := FWfHigh;
|
||||||
|
for HistIdx := 0 to High(Hist) do
|
||||||
|
begin
|
||||||
|
Inc(CumCount, Hist[HistIdx]);
|
||||||
|
if CumCount >= LowTargetCount then
|
||||||
|
begin
|
||||||
|
NoiseFloorDB := HistMinDB + (HistIdx + 0.5) * HistStepDB;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
CumCount := 0;
|
||||||
|
for HistIdx := 0 to High(Hist) do
|
||||||
|
begin
|
||||||
|
Inc(CumCount, Hist[HistIdx]);
|
||||||
|
if CumCount >= HighTargetCount then
|
||||||
|
begin
|
||||||
|
SignalTopDB := HistMinDB + (HistIdx + 0.5) * HistStepDB;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if FWfAGCEnabled then
|
||||||
|
begin
|
||||||
|
TargetLow := NoiseFloorDB + WF_AUTO_OFFSET + FWfAGCOffset;
|
||||||
|
if FWfNFEnabled then TargetHigh := Max(TargetLow + WF_MIN_RANGE, SignalTopDB + 6.0)
|
||||||
|
else TargetHigh := TargetLow + 52.0;
|
||||||
|
if TargetHigh > TargetLow + WF_MAX_RANGE then TargetHigh := TargetLow + WF_MAX_RANGE;
|
||||||
|
FWfLow := FWfLow + ALPHA_LOW * (TargetLow - FWfLow);
|
||||||
|
FWfHigh := FWfHigh + ALPHA_HIGH * (TargetHigh - FWfHigh);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.EnsureTexture(W, H: Integer);
|
||||||
|
var
|
||||||
|
Bg: LongWord;
|
||||||
|
Pixels: array of Byte;
|
||||||
|
I: Integer;
|
||||||
|
begin
|
||||||
|
if (W <= 0) or (H <= 0) then Exit;
|
||||||
|
if (FTexture <> 0) and (FTexW = W) and (FTexH = H) then Exit;
|
||||||
|
DeleteTexture(FTexture);
|
||||||
|
FTexW := W;
|
||||||
|
FTexH := H;
|
||||||
|
FLatestRow := 0;
|
||||||
|
SetLength(FRowPixels, W * 4);
|
||||||
|
SetLength(Pixels, W * H * 4);
|
||||||
|
Bg := ColorForDB(-130.0, FWfManualLow, FWfManualHigh);
|
||||||
|
for I := 0 to W * H - 1 do
|
||||||
|
begin
|
||||||
|
Pixels[I * 4] := Byte(Bg shr 16);
|
||||||
|
Pixels[I * 4 + 1] := Byte(Bg shr 8);
|
||||||
|
Pixels[I * 4 + 2] := Byte(Bg);
|
||||||
|
Pixels[I * 4 + 3] := $FF;
|
||||||
|
end;
|
||||||
|
glGenTextures(1, @FTexture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, FTexture);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA,
|
||||||
|
GL_UNSIGNED_BYTE, @Pixels[0]);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.UploadCurrentRow(W: Integer);
|
||||||
|
var
|
||||||
|
X, SrcCount, S0, S1, Off: Integer;
|
||||||
|
SrcF, Step, Frac, DBV, WfHigh, WfLow: Double;
|
||||||
|
Pal: LongWord;
|
||||||
|
begin
|
||||||
|
if (FTexture = 0) or (W <= 0) then Exit;
|
||||||
|
UpdateLevels;
|
||||||
|
WfHigh := FWfHigh;
|
||||||
|
WfLow := FWfLow;
|
||||||
|
if not FWfAGCEnabled then
|
||||||
|
begin
|
||||||
|
WfHigh := FWfManualHigh;
|
||||||
|
WfLow := FWfManualLow;
|
||||||
|
end;
|
||||||
|
if WfHigh < WfLow + 40.0 then WfHigh := WfLow + 40.0;
|
||||||
|
if WfHigh > 0.0 then WfHigh := 0.0;
|
||||||
|
if WfLow < -160 then WfLow := -160;
|
||||||
|
|
||||||
|
SrcCount := EnsureRange(FWaterfallBufCount, 2, 1024);
|
||||||
|
Step := (SrcCount - 1.0) / Max(1, W - 1);
|
||||||
|
SrcF := 0.0;
|
||||||
|
for X := 0 to W - 1 do
|
||||||
|
begin
|
||||||
|
if FTXMode and (FTXSpanHz > 0) and (FSpanHz > 0) then
|
||||||
|
begin
|
||||||
|
DBV := FCenterFreq - FSpanHz * 0.5 + X * FSpanHz / Max(1, W - 1) - FTXFreq;
|
||||||
|
if (DBV < -FTXSpanHz * 0.5) or (DBV > FTXSpanHz * 0.5) then
|
||||||
|
DBV := WfLow
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
SrcF := (DBV + FTXSpanHz * 0.5) / FTXSpanHz * (SrcCount - 1);
|
||||||
|
S0 := Min(Trunc(SrcF), SrcCount - 2);
|
||||||
|
S1 := S0 + 1;
|
||||||
|
Frac := SrcF - S0;
|
||||||
|
DBV := FWaterfallBuf[S0] * (1.0 - Frac) + FWaterfallBuf[S1] * Frac;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
S0 := Min(Trunc(SrcF), SrcCount - 2);
|
||||||
|
S1 := S0 + 1;
|
||||||
|
Frac := SrcF - S0;
|
||||||
|
DBV := FWaterfallBuf[S0] * (1.0 - Frac) + FWaterfallBuf[S1] * Frac;
|
||||||
|
end;
|
||||||
|
Pal := ColorForDB(DBV, WfLow, WfHigh);
|
||||||
|
Off := X * 4;
|
||||||
|
FRowPixels[Off] := Byte(Pal shr 16);
|
||||||
|
FRowPixels[Off + 1] := Byte(Pal shr 8);
|
||||||
|
FRowPixels[Off + 2] := Byte(Pal);
|
||||||
|
FRowPixels[Off + 3] := $FF;
|
||||||
|
SrcF := SrcF + Step;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Dec(FLatestRow);
|
||||||
|
if FLatestRow < 0 then FLatestRow := FTexH - 1;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, FTexture);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, FLatestRow, W, 1, GL_RGBA,
|
||||||
|
GL_UNSIGNED_BYTE, @FRowPixels[0]);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.DrawTextureWrapped(W, H: Integer);
|
||||||
|
var
|
||||||
|
FirstRows, SecondRows: Integer;
|
||||||
|
V0, V1: GLFloat;
|
||||||
|
begin
|
||||||
|
if FTexture = 0 then Exit;
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, FTexture);
|
||||||
|
glColor4f(1, 1, 1, 1);
|
||||||
|
|
||||||
|
FirstRows := FTexH - FLatestRow;
|
||||||
|
V0 := FLatestRow / FTexH;
|
||||||
|
V1 := 1.0;
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
glTexCoord2f(0, V0); glVertex2f(0, 0);
|
||||||
|
glTexCoord2f(1, V0); glVertex2f(W, 0);
|
||||||
|
glTexCoord2f(1, V1); glVertex2f(W, FirstRows);
|
||||||
|
glTexCoord2f(0, V1); glVertex2f(0, FirstRows);
|
||||||
|
glEnd;
|
||||||
|
|
||||||
|
SecondRows := FLatestRow;
|
||||||
|
if SecondRows > 0 then
|
||||||
|
begin
|
||||||
|
V0 := 0.0;
|
||||||
|
V1 := FLatestRow / FTexH;
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
glTexCoord2f(0, V0); glVertex2f(0, FirstRows);
|
||||||
|
glTexCoord2f(1, V0); glVertex2f(W, FirstRows);
|
||||||
|
glTexCoord2f(1, V1); glVertex2f(W, H);
|
||||||
|
glTexCoord2f(0, V1); glVertex2f(0, H);
|
||||||
|
glEnd;
|
||||||
|
end;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.UploadMarkerText(const S: string);
|
||||||
|
var
|
||||||
|
B: TBitmap;
|
||||||
|
X, Y, I: Integer;
|
||||||
|
Src: PByte;
|
||||||
|
Buf: array of Byte;
|
||||||
|
A: Byte;
|
||||||
|
begin
|
||||||
|
if S = '' then Exit;
|
||||||
|
B := TBitmap.Create;
|
||||||
|
try
|
||||||
|
B.PixelFormat := pf32bit;
|
||||||
|
B.SetSize(8, 8);
|
||||||
|
B.Canvas.Font.Name := 'Courier New';
|
||||||
|
B.Canvas.Font.Size := 7;
|
||||||
|
FMarkerTexW := B.Canvas.TextWidth(S) + 4;
|
||||||
|
FMarkerTexH := B.Canvas.TextHeight(S) + 2;
|
||||||
|
B.SetSize(FMarkerTexW, FMarkerTexH);
|
||||||
|
B.Canvas.Brush.Color := clBlack;
|
||||||
|
B.Canvas.FillRect(0, 0, FMarkerTexW, FMarkerTexH);
|
||||||
|
B.Canvas.Font.Name := 'Courier New';
|
||||||
|
B.Canvas.Font.Size := 7;
|
||||||
|
B.Canvas.Font.Color := clWhite;
|
||||||
|
B.Canvas.Brush.Style := bsClear;
|
||||||
|
B.Canvas.TextOut(2, 1, S);
|
||||||
|
SetLength(Buf, FMarkerTexW * FMarkerTexH * 4);
|
||||||
|
I := 0;
|
||||||
|
B.BeginUpdate(False);
|
||||||
|
try
|
||||||
|
for Y := 0 to FMarkerTexH - 1 do
|
||||||
|
begin
|
||||||
|
Src := PByte(B.ScanLine[Y]);
|
||||||
|
for X := 0 to FMarkerTexW - 1 do
|
||||||
|
begin
|
||||||
|
A := Max(Src[0], Max(Src[1], Src[2]));
|
||||||
|
Buf[I] := $FF;
|
||||||
|
Buf[I + 1] := $44;
|
||||||
|
Buf[I + 2] := $44;
|
||||||
|
Buf[I + 3] := A;
|
||||||
|
Inc(Src, 4);
|
||||||
|
Inc(I, 4);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
B.EndUpdate(False);
|
||||||
|
end;
|
||||||
|
if FMarkerTex = 0 then glGenTextures(1, @FMarkerTex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, FMarkerTex);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FMarkerTexW, FMarkerTexH, 0,
|
||||||
|
GL_RGBA, GL_UNSIGNED_BYTE, @Buf[0]);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
FMarkerText := S;
|
||||||
|
finally
|
||||||
|
B.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.DrawMarker(W, H: Integer);
|
||||||
|
var
|
||||||
|
MX, TX: Integer;
|
||||||
|
MarkerFreq: Double;
|
||||||
|
S: string;
|
||||||
|
begin
|
||||||
|
if not FMarkerActive then Exit;
|
||||||
|
MX := Round(FMarkerX / 1000.0 * W);
|
||||||
|
if (MX < 0) or (MX >= W) then Exit;
|
||||||
|
glColor4f(1.0, $44 / 255.0, $44 / 255.0, 1);
|
||||||
|
glBegin(GL_LINES);
|
||||||
|
glVertex2f(MX + 0.5, 0);
|
||||||
|
glVertex2f(MX + 0.5, H);
|
||||||
|
glEnd;
|
||||||
|
MarkerFreq := (FCenterFreq - FSpanHz / 2) + FMarkerX / 1000.0 * FSpanHz;
|
||||||
|
S := FormatFreqGL(Round(MarkerFreq));
|
||||||
|
if (S <> FMarkerText) or (FMarkerTex = 0) then UploadMarkerText(S);
|
||||||
|
if FMarkerTex = 0 then Exit;
|
||||||
|
if MX + 4 + FMarkerTexW < W then TX := MX + 4
|
||||||
|
else TX := MX - 4 - FMarkerTexW;
|
||||||
|
glEnable(GL_TEXTURE_2D);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, FMarkerTex);
|
||||||
|
glColor4f(1, 1, 1, 1);
|
||||||
|
glBegin(GL_QUADS);
|
||||||
|
glTexCoord2f(0, 0); glVertex2f(TX, 4);
|
||||||
|
glTexCoord2f(1, 0); glVertex2f(TX + FMarkerTexW, 4);
|
||||||
|
glTexCoord2f(1, 1); glVertex2f(TX + FMarkerTexW, 4 + FMarkerTexH);
|
||||||
|
glTexCoord2f(0, 1); glVertex2f(TX, 4 + FMarkerTexH);
|
||||||
|
glEnd;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
glDisable(GL_TEXTURE_2D);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.SetWaterfallBitmapSize(W, H: Integer);
|
||||||
|
begin
|
||||||
|
if (W <= 0) or (H <= 0) then Exit;
|
||||||
|
if Assigned(FGLControl) and FGLControl.MakeCurrent then
|
||||||
|
EnsureTexture(W, H);
|
||||||
|
FWaterfallDirty := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.DrawWaterfall;
|
||||||
|
begin
|
||||||
|
if Assigned(FGLControl) then FGLControl.Invalidate;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.PaintWaterfall(Sender: TObject);
|
||||||
|
var
|
||||||
|
W, H: Integer;
|
||||||
|
R, G, B: GLFloat;
|
||||||
|
begin
|
||||||
|
if Sender is TOpenGLControl then FGLControl := TOpenGLControl(Sender);
|
||||||
|
if (FGLControl = nil) or (not FGLControl.MakeCurrent) then Exit;
|
||||||
|
W := FGLControl.Width;
|
||||||
|
H := FGLControl.Height;
|
||||||
|
if (W <= 0) or (H <= 0) then Exit;
|
||||||
|
|
||||||
|
glViewport(0, 0, W, H);
|
||||||
|
glMatrixMode(GL_PROJECTION);
|
||||||
|
glLoadIdentity;
|
||||||
|
glOrtho(0, W, H, 0, -1, 1);
|
||||||
|
glMatrixMode(GL_MODELVIEW);
|
||||||
|
glLoadIdentity;
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
R := (FTheme.Panel and $FF) / 255.0;
|
||||||
|
G := ((FTheme.Panel shr 8) and $FF) / 255.0;
|
||||||
|
B := ((FTheme.Panel shr 16) and $FF) / 255.0;
|
||||||
|
glClearColor(R, G, B, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
EnsureTexture(W, H);
|
||||||
|
if FWaterfallDirty then
|
||||||
|
begin
|
||||||
|
UploadCurrentRow(W);
|
||||||
|
FWaterfallDirty := False;
|
||||||
|
end;
|
||||||
|
DrawTextureWrapped(W, H);
|
||||||
|
DrawMarker(W, H);
|
||||||
|
FGLControl.SwapBuffers;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.SetTheme(const T: TAppTheme);
|
||||||
|
begin
|
||||||
|
inherited SetTheme(T);
|
||||||
|
if Assigned(FGLControl) and FGLControl.MakeCurrent then
|
||||||
|
DeleteTexture(FTexture);
|
||||||
|
FTexW := 0;
|
||||||
|
FTexH := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.ResetWfBuf;
|
||||||
|
begin
|
||||||
|
inherited ResetWfBuf;
|
||||||
|
if Assigned(FGLControl) and FGLControl.MakeCurrent then
|
||||||
|
DeleteTexture(FTexture);
|
||||||
|
FTexW := 0;
|
||||||
|
FTexH := 0;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TWaterfallViewOpenGL.ResetWfAvgBuf;
|
||||||
|
begin
|
||||||
|
inherited ResetWfAvgBuf;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -222,6 +222,10 @@
|
|||||||
<Filename Value="WaterfallView.pas"/>
|
<Filename Value="WaterfallView.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
</Unit>
|
</Unit>
|
||||||
|
<Unit>
|
||||||
|
<Filename Value="WaterfallViewOpengl.pas"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
</Unit>
|
||||||
<Unit>
|
<Unit>
|
||||||
<Filename Value="SMeterView.pas"/>
|
<Filename Value="SMeterView.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user