Skip to content

Commit

Permalink
Initial implementation of spectrogram rendering shader appears functi…
Browse files Browse the repository at this point in the history
…onal
  • Loading branch information
azonenberg committed Sep 19, 2023
1 parent 2fdce42 commit bf81dbc
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 32 deletions.
2 changes: 1 addition & 1 deletion lib
29 changes: 18 additions & 11 deletions src/ngscopeclient/WaveformArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "MainWindow.h"
#include "../../scopehal/TwoLevelTrigger.h"
#include "../../scopeprotocols/EyePattern.h"
#include "../../scopeprotocols/SpectrogramFilter.h"
#include "../../scopeprotocols/Waterfall.h"
#include "../../scopehal/DensityFunctionWaveform.h"

Expand Down Expand Up @@ -337,9 +338,11 @@ StreamDescriptor WaveformArea::GetFirstAnalogStream()
}

/**
@brief Returns the first analog stream or eye pattern displayed in this area.
@brief Returns the first analog, spectrogram or eye pattern stream displayed in this area.
If no analog waveforms or eye patterns are visible, returns a null stream.
TODO: this really means "has a useful Y axis"
If no suitable waveforms returns a null stream.
*/
StreamDescriptor WaveformArea::GetFirstAnalogOrEyeStream()
{
Expand All @@ -348,6 +351,8 @@ StreamDescriptor WaveformArea::GetFirstAnalogOrEyeStream()
auto stream = chan->GetStream();
if(stream.GetType() == Stream::STREAM_TYPE_ANALOG)
return stream;
if(stream.GetType() == Stream::STREAM_TYPE_SPECTROGRAM)
return stream;
if(stream.GetType() == Stream::STREAM_TYPE_EYE)
return stream;
}
Expand Down Expand Up @@ -1839,7 +1844,7 @@ void WaveformArea::ToneMapSpectrogramWaveform(std::shared_ptr<DisplayedChannel>
if(tex == nullptr)
return;

auto data = dynamic_cast<DensityFunctionWaveform*>(channel->GetStream().GetData());
auto data = dynamic_cast<SpectrogramWaveform*>(channel->GetStream().GetData());
if(data == nullptr)
return;

Expand Down Expand Up @@ -1867,16 +1872,18 @@ void WaveformArea::ToneMapSpectrogramWaveform(std::shared_ptr<DisplayedChannel>
int64_t offset = m_group->GetXAxisOffset();
int64_t offset_samples = (offset - data->m_triggerPhase) / data->m_timescale;

double pixelsPerX = m_group->GetPixelsPerXUnit();
double xscale = data->m_timescale * pixelsPerX;
//Invert X (and Y) scales because multiply in the shader is faster than divide
double xscale = 1.0 / (data->m_timescale * m_group->GetPixelsPerXUnit());

SpectrogramToneMapArgs args(width, height, m_width, m_height, offset_samples, xscale );
pipe->Dispatch(cmdbuf, args, GetComputeBlockCount(m_width, 64), m_height);
//Rescale Y offset to screen pixels
//Note that we actually care about offset from our *bottom*, but GetOffset() returns offset from our *midpoint*
int32_t yoff = YAxisUnitsToPixels(-channel->GetStream().GetOffset()) - m_height/2;

LogDebug("offset_samples = %ld\n", offset_samples);
LogDebug("xscale = %f\n", xscale);
LogDebug("w/h = %zu, %zu\n", width, height);
LogDebug("m_w/m_h = %f, %f\n", m_width, m_height);
//Rescale Y to "spectrogram bins per pixel" vs "Hz per pixel"
float yscale = 1.0 / (m_pixelsPerYAxisUnit * data->GetBinSize());

SpectrogramToneMapArgs args(width, height, m_width, m_height, offset_samples, xscale, yoff, yscale);
pipe->Dispatch(cmdbuf, args, GetComputeBlockCount(m_width, 64), m_height);

//Add a barrier before we read from the fragment shader
vk::ImageSubresourceRange range(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1);
Expand Down
11 changes: 8 additions & 3 deletions src/ngscopeclient/WaveformArea.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,26 @@ class WaterfallToneMapArgs
class SpectrogramToneMapArgs
{
public:
SpectrogramToneMapArgs(uint32_t w, uint32_t h, uint32_t outwidth, uint32_t outheight, uint32_t o, float x)
SpectrogramToneMapArgs(uint32_t w, uint32_t h, uint32_t outwidth, uint32_t outheight, uint32_t xo,
float x, int32_t yo, float y)
: m_width(w)
, m_height(h)
, m_outwidth(outwidth)
, m_outheight(outheight)
, m_offsetSamples(o)
, m_xoff(xo)
, m_xscale(x)
, m_yoff(yo)
, m_yscale(y)
{}

uint32_t m_width;
uint32_t m_height;
uint32_t m_outwidth;
uint32_t m_outheight;
uint32_t m_offsetSamples;
uint32_t m_xoff;
float m_xscale;
int32_t m_yoff;
float m_yscale;
};

struct ConfigPushConstants
Expand Down
49 changes: 32 additions & 17 deletions src/ngscopeclient/shaders/SpectrogramToneMap.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ layout(std430, push_constant) uniform constants
uint height;
uint outwidth;
uint outheight;
uint offset_samples;
uint xoff;
float xscale;
uint yoff;
float yscale;
};

layout(local_size_x=64, local_size_y=1, local_size_z=1) in;
Expand All @@ -58,38 +60,51 @@ void main()
if(gl_GlobalInvocationID.y >= outheight)
return;

//Move the entire output display down if needed, so topmost (newest) row is always visible
uint yreal = gl_GlobalInvocationID.y + (height - outheight);

//Figure out which input pixel(s) contribute to this output pixel
uint istart = uint(floor(gl_GlobalInvocationID.x / xscale)) + offset_samples;
uint iend = uint(floor((gl_GlobalInvocationID.x + 1) / xscale)) + offset_samples;
uint xstart = uint(floor(gl_GlobalInvocationID.x * xscale)) + xoff;
uint xend = uint(floor((gl_GlobalInvocationID.x + 1) * xscale)) + xoff;

uint ystart = uint(floor((gl_GlobalInvocationID.y + yoff) * yscale));
uint yend = uint(floor(((gl_GlobalInvocationID.y + yoff) + 1) * yscale));

//Cap number of input values per pixel if really zoomed out
uint xmaxbins = 256;
if( (xend - xstart) > xmaxbins)
xend = xstart + xmaxbins;

//Cap number of FFT bins per pixel if really zoomed out
uint maxbins = 256;
if( (iend - istart) > maxbins)
iend = istart + maxbins;
uint ymaxbins = 256;
if( (yend - ystart) > ymaxbins)
yend = ystart + ymaxbins;

float clampedValue = 0;

//If out of bounds, nothing to do
if( (iend < 0) || (istart >= width) )
if( (xend < 0) || (xstart >= width) || (ystart < 0) || (yend >= height) )
{
}

else
{
istart = max(0, istart);
iend = max(0, iend);
//Clamp coordinates
xstart = max(0, xstart);
xend = max(0, xend);
xstart = min(width-1, xstart);
xend = min(width-1, xend);

istart = min(width-1, istart);
iend = min(width-1, iend);
ystart = max(0, ystart);
yend = max(0, yend);
ystart = min(height-1, ystart);
yend = min(height-1, yend);

//Intensity graded grayscale input
//Highest value of the input is our output (this keeps peaks from fading away as we zoom out)
float pixval = 0;
for(uint i=istart; i <= iend; i++)
pixval = max(pixval, pixels[yreal*width + i]);
for(uint y=ystart; y <= yend; y++)
{
uint base = ystart*width;
for(uint x=xstart; x <= xend; x++)
pixval = max(pixval, pixels[base+x]);
}

//Clamp to texture bounds
clampedValue = min(pixval, 0.99);
Expand Down

0 comments on commit bf81dbc

Please sign in to comment.