mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-25 01:18:38 -05:00
Add 3D spectrogram
This commit is contained in:
parent
7d1e8f8028
commit
d441e6d475
BIN
doc/img/MainWindow_graphics.png
Normal file
BIN
doc/img/MainWindow_graphics.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
@ -172,6 +172,7 @@ set(sdrbase_SOURCES
|
||||
util/aprs.cpp
|
||||
util/astronomy.cpp
|
||||
util/azel.cpp
|
||||
util/colormap.cpp
|
||||
util/coordinates.cpp
|
||||
util/crc.cpp
|
||||
util/CRC64.cpp
|
||||
@ -383,6 +384,7 @@ set(sdrbase_HEADERS
|
||||
util/aprs.h
|
||||
util/astronomy.h
|
||||
util/azel.h
|
||||
util/colormap.h
|
||||
util/coordinates.h
|
||||
util/CRC64.h
|
||||
util/csv.h
|
||||
|
@ -49,6 +49,7 @@ void SpectrumSettings::resetToDefaults()
|
||||
m_displayCurrent = true;
|
||||
m_displayWaterfall = true;
|
||||
m_invertedWaterfall = true;
|
||||
m_display3DSpectrogram = false;
|
||||
m_displayMaxHold = false;
|
||||
m_displayHistogram = false;
|
||||
m_displayGrid = false;
|
||||
@ -64,6 +65,8 @@ void SpectrumSettings::resetToDefaults()
|
||||
m_markersDisplay = MarkersDisplayNone;
|
||||
m_useCalibration = false;
|
||||
m_calibrationInterpMode = CalibInterpLinear;
|
||||
m_3DSpectrogramStyle = Outline;
|
||||
m_3DSpectrogramColorMap = "Angel";
|
||||
}
|
||||
|
||||
QByteArray SpectrumSettings::serialize() const
|
||||
@ -99,6 +102,9 @@ QByteArray SpectrumSettings::serialize() const
|
||||
s.writeS32(28, (int) m_markersDisplay);
|
||||
s.writeBool(29, m_useCalibration);
|
||||
s.writeS32(30, (int) m_calibrationInterpMode);
|
||||
s.writeBool(31, m_display3DSpectrogram);
|
||||
s.writeS32(32, (int) m_3DSpectrogramStyle);
|
||||
s.writeString(33, m_3DSpectrogramColorMap);
|
||||
s.writeS32(100, m_histogramMarkers.size());
|
||||
|
||||
for (int i = 0; i < m_histogramMarkers.size(); i++) {
|
||||
@ -196,6 +202,9 @@ bool SpectrumSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(29, &m_useCalibration, false);
|
||||
d.readS32(30, &tmp, 0);
|
||||
m_calibrationInterpMode = (CalibrationInterpolationMode) tmp;
|
||||
d.readBool(31, &m_display3DSpectrogram, false);
|
||||
d.readS32(32, (int*)&m_3DSpectrogramStyle, (int)Outline);
|
||||
d.readString(33, &m_3DSpectrogramColorMap, "Angel");
|
||||
|
||||
int histogramMarkersSize;
|
||||
d.readS32(100, &histogramMarkersSize, 0);
|
||||
|
@ -52,6 +52,15 @@ public:
|
||||
CalibInterpLog //!< log power (dB linear)
|
||||
};
|
||||
|
||||
enum SpectrogramStyle
|
||||
{
|
||||
Points,
|
||||
Lines,
|
||||
Solid,
|
||||
Outline,
|
||||
Shaded
|
||||
};
|
||||
|
||||
int m_fftSize;
|
||||
int m_fftOverlap;
|
||||
FFTWindow::Function m_fftWindow;
|
||||
@ -66,6 +75,7 @@ public:
|
||||
bool m_displayWaterfall;
|
||||
bool m_invertedWaterfall;
|
||||
Real m_waterfallShare;
|
||||
bool m_display3DSpectrogram;
|
||||
bool m_displayMaxHold;
|
||||
bool m_displayCurrent;
|
||||
bool m_displayHistogram;
|
||||
@ -86,6 +96,8 @@ public:
|
||||
QList<SpectrumCalibrationPoint> m_calibrationPoints;
|
||||
bool m_useCalibration;
|
||||
CalibrationInterpolationMode m_calibrationInterpMode; //!< How is power interpolated between calibration points
|
||||
SpectrogramStyle m_3DSpectrogramStyle;
|
||||
QString m_3DSpectrogramColorMap;
|
||||
static const int m_log2FFTSizeMin = 6; // 64
|
||||
static const int m_log2FFTSizeMax = 15; // 32k
|
||||
|
||||
|
@ -183,6 +183,13 @@ public:
|
||||
const AudioDeviceManager *getAudioDeviceManager() const { return m_audioDeviceManager; }
|
||||
void setAudioDeviceManager(AudioDeviceManager *audioDeviceManager) { m_audioDeviceManager = audioDeviceManager; }
|
||||
|
||||
int getMultisampling() const { return m_preferences.getMultisampling(); }
|
||||
void setMultisampling(int samples)
|
||||
{
|
||||
m_preferences.setMultisampling(samples);
|
||||
emit preferenceChanged(Preferences::Multisampling);
|
||||
}
|
||||
|
||||
signals:
|
||||
void preferenceChanged(int);
|
||||
|
||||
|
@ -21,6 +21,7 @@ void Preferences::resetToDefaults()
|
||||
m_logFileName = "sdrangel.log";
|
||||
m_consoleMinLogLevel = QtDebugMsg;
|
||||
m_fileMinLogLevel = QtDebugMsg;
|
||||
m_multisampling = 0;
|
||||
}
|
||||
|
||||
QByteArray Preferences::serialize() const
|
||||
@ -39,6 +40,7 @@ QByteArray Preferences::serialize() const
|
||||
s.writeString((int) StationName, m_stationName);
|
||||
s.writeFloat((int) Altitude, m_altitude);
|
||||
s.writeS32((int) SourceItemIndex, m_sourceItemIndex);
|
||||
s.writeS32((int) Multisampling, m_multisampling);
|
||||
return s.final();
|
||||
}
|
||||
|
||||
@ -92,6 +94,8 @@ bool Preferences::deserialize(const QByteArray& data)
|
||||
m_fileMinLogLevel = QtDebugMsg;
|
||||
}
|
||||
|
||||
d.readS32((int) Multisampling, &m_multisampling, 0);
|
||||
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
|
@ -21,7 +21,8 @@ public:
|
||||
FileMinLogLevel,
|
||||
StationName,
|
||||
Altitude,
|
||||
SourceItemIndex
|
||||
SourceItemIndex,
|
||||
Multisampling
|
||||
};
|
||||
|
||||
Preferences();
|
||||
@ -71,6 +72,9 @@ public:
|
||||
const QString& getLogFileName() const { return m_logFileName; }
|
||||
void setLogFileName(const QString& value) { m_logFileName = value; }
|
||||
|
||||
int getMultisampling() const { return m_multisampling; }
|
||||
void setMultisampling(int samples) { m_multisampling = samples; }
|
||||
|
||||
protected:
|
||||
QString m_sourceDevice; //!< Identification of the source used in R0 tab (GUI flavor) at startup
|
||||
int m_sourceIndex; //!< Index of the source used in R0 tab (GUI flavor) at startup
|
||||
@ -88,6 +92,8 @@ protected:
|
||||
QtMsgType m_fileMinLogLevel;
|
||||
bool m_useLogFile;
|
||||
QString m_logFileName;
|
||||
|
||||
int m_multisampling; //!< Number of samples to use for multisampling anti-aliasing (typically 0 or 4)
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PREFERENCES_H
|
||||
|
5258
sdrbase/util/colormap.cpp
Normal file
5258
sdrbase/util/colormap.cpp
Normal file
File diff suppressed because it is too large
Load Diff
62
sdrbase/util/colormap.h
Normal file
62
sdrbase/util/colormap.h
Normal file
@ -0,0 +1,62 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_COLORMAP_H
|
||||
#define INCLUDE_COLORMAP_H
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
// 256-entry floating point RGB color maps.
|
||||
// "Angel" is SDRangel's waterfall colormap
|
||||
// Some common maps from matplotlib
|
||||
// Scientific colour maps from:
|
||||
// https://www.nature.com/articles/s41467-020-19160-7
|
||||
// https://zenodo.org/record/5501399#.YqhaAu7MLAQ
|
||||
class SDRBASE_API ColorMap
|
||||
{
|
||||
public:
|
||||
static QStringList getColorMapNames();
|
||||
static const float *getColorMap(const QString &name);
|
||||
static constexpr int m_size = 256*3;
|
||||
|
||||
private:
|
||||
static QHash<QString, const float *> m_colorMaps;
|
||||
static const float m_angel[m_size];
|
||||
static const float m_jet[m_size];
|
||||
static const float m_turbo[m_size];
|
||||
static const float m_parula[m_size];
|
||||
static const float m_hot[m_size];
|
||||
static const float m_cool[m_size];
|
||||
static const float m_batlow[m_size];
|
||||
static const float m_hawaii[m_size];
|
||||
static const float m_acton[m_size];
|
||||
static const float m_imola[m_size];
|
||||
static const float m_tokyo[m_size];
|
||||
static const float m_lapaz[m_size];
|
||||
static const float m_buda[m_size];
|
||||
static const float m_devon[m_size];
|
||||
static const float m_lajolla[m_size];
|
||||
static const float m_bamako[m_size];
|
||||
static const float m_plasma[m_size];
|
||||
static const float m_rainbow[m_size];
|
||||
static const float m_prism[m_size];
|
||||
static const float m_viridis[m_size];
|
||||
};
|
||||
|
||||
#endif
|
@ -45,10 +45,12 @@ set(sdrgui_SOURCES
|
||||
gui/glscopegui.cpp
|
||||
gui/glshadercolors.cpp
|
||||
gui/glshadersimple.cpp
|
||||
gui/glshaderspectrogram.cpp
|
||||
gui/glshadertextured.cpp
|
||||
gui/glshadertvarray.cpp
|
||||
gui/glspectrum.cpp
|
||||
gui/glspectrumgui.cpp
|
||||
gui/graphicsdialog.cpp
|
||||
gui/graphicsviewzoom.cpp
|
||||
gui/httpdownloadmanagergui.cpp
|
||||
gui/indicator.cpp
|
||||
@ -144,10 +146,12 @@ set(sdrgui_HEADERS
|
||||
gui/glscopegui.h
|
||||
gui/glshadercolors.h
|
||||
gui/glshadersimple.h
|
||||
gui/glshaderspectrogram.h
|
||||
gui/glshadertvarray.h
|
||||
gui/glshadertextured.h
|
||||
gui/glspectrum.h
|
||||
gui/glspectrumgui.h
|
||||
gui/graphicsdialog.h
|
||||
gui/graphicsviewzoom.h
|
||||
gui/httpdownloadmanagergui.h
|
||||
gui/indicator.h
|
||||
@ -224,6 +228,7 @@ set(sdrgui_FORMS
|
||||
gui/fftwisdomdialog.ui
|
||||
gui/glscopegui.ui
|
||||
gui/glspectrumgui.ui
|
||||
gui/graphicsdialog.ui
|
||||
gui/pluginsdialog.ui
|
||||
gui/audiodialog.ui
|
||||
gui/audioselectdialog.ui
|
||||
|
@ -60,32 +60,32 @@ void GLShaderSimple::initializeGL()
|
||||
m_program->release();
|
||||
}
|
||||
|
||||
void GLShaderSimple::drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
draw(GL_POINTS, transformMatrix, color, vertices, nbVertices);
|
||||
draw(GL_POINTS, transformMatrix, color, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
|
||||
void GLShaderSimple::drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
draw(GL_LINE_STRIP, transformMatrix, color, vertices, nbVertices);
|
||||
draw(GL_LINE_STRIP, transformMatrix, color, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
|
||||
void GLShaderSimple::drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
draw(GL_LINES, transformMatrix, color, vertices, nbVertices);
|
||||
draw(GL_LINES, transformMatrix, color, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
|
||||
void GLShaderSimple::drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
draw(GL_LINE_LOOP, transformMatrix, color, vertices, nbVertices);
|
||||
draw(GL_LINE_LOOP, transformMatrix, color, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
|
||||
void GLShaderSimple::drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
draw(GL_TRIANGLE_FAN, transformMatrix, color, vertices, nbVertices);
|
||||
draw(GL_TRIANGLE_FAN, transformMatrix, color, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
|
||||
void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
|
||||
m_program->bind();
|
||||
@ -95,7 +95,7 @@ void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix,
|
||||
f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
f->glLineWidth(1.0f);
|
||||
f->glEnableVertexAttribArray(0); // vertex
|
||||
f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
f->glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
f->glDrawArrays(mode, 0, nbVertices);
|
||||
f->glDisableVertexAttribArray(0);
|
||||
m_program->release();
|
||||
|
@ -35,15 +35,15 @@ public:
|
||||
~GLShaderSimple();
|
||||
|
||||
void initializeGL();
|
||||
void drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
|
||||
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents);
|
||||
|
||||
QOpenGLShaderProgram *m_program;
|
||||
int m_matrixLoc;
|
||||
|
758
sdrgui/gui/glshaderspectrogram.cpp
Normal file
758
sdrgui/gui/glshaderspectrogram.cpp
Normal file
@ -0,0 +1,758 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 Edouard Griffiths, F4EXB. //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLContext>
|
||||
#include <QtOpenGL>
|
||||
#include <QImage>
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector4D>
|
||||
#include <QDebug>
|
||||
|
||||
#include "gui/glshaderspectrogram.h"
|
||||
#include "util/colormap.h"
|
||||
|
||||
GLShaderSpectrogram::GLShaderSpectrogram() :
|
||||
m_programShaded(nullptr),
|
||||
m_programSimple(nullptr),
|
||||
m_texture(nullptr),
|
||||
m_textureId(0),
|
||||
m_colorMapTexture(nullptr),
|
||||
m_colorMapTextureId(0),
|
||||
m_programForLocs(nullptr),
|
||||
m_textureTransformLoc(0),
|
||||
m_vertexTransformLoc(0),
|
||||
m_textureLoc(0),
|
||||
m_limitLoc(0),
|
||||
m_brightnessLoc(0),
|
||||
m_colorMapLoc(0),
|
||||
m_lightDirLoc(0),
|
||||
m_lightPosLoc(0),
|
||||
m_useImmutableStorage(true),
|
||||
m_vertexBuf(QOpenGLBuffer::VertexBuffer),
|
||||
m_index0Buf(QOpenGLBuffer::IndexBuffer),
|
||||
m_index1Buf(QOpenGLBuffer::IndexBuffer),
|
||||
m_translateX(0.0),
|
||||
m_translateY(0.0),
|
||||
m_translateZ(0.0),
|
||||
m_rotX(-45.0),
|
||||
m_rotY(0.0),
|
||||
m_rotZ(0.0),
|
||||
m_scaleX(1.0),
|
||||
m_scaleY(1.0),
|
||||
m_scaleZ(1.0),
|
||||
m_userScaleZ(1.0),
|
||||
m_verticalAngle(45.0f),
|
||||
m_aspectRatio(1600.0/1200.0),
|
||||
m_lightTranslateX(0.0),
|
||||
m_lightTranslateY(0.0),
|
||||
m_lightTranslateZ(0.0),
|
||||
m_lightRotX(0.0),
|
||||
m_lightRotY(0.0),
|
||||
m_lightRotZ(0.0),
|
||||
m_gridElements(1024)
|
||||
{
|
||||
}
|
||||
|
||||
GLShaderSpectrogram::~GLShaderSpectrogram()
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initializeGL()
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
m_useImmutableStorage = useImmutableStorage();
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: m_useImmutableStorage: " << m_useImmutableStorage;
|
||||
|
||||
m_programShaded = new QOpenGLShaderProgram;
|
||||
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShader)) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error in vertex shader: " << m_programShaded->log();
|
||||
}
|
||||
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Geometry, m_geometryShader)) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error in geometry shader: " << m_programShaded->log();
|
||||
}
|
||||
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderShaded)) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error in fragment shader: " << m_programShaded->log();
|
||||
}
|
||||
if (!m_programShaded->link()) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error linking shader: " << m_programShaded->log();
|
||||
}
|
||||
|
||||
m_programSimple = new QOpenGLShaderProgram;
|
||||
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShader)) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error in vertex shader: " << m_programSimple->log();
|
||||
}
|
||||
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSimple)) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error in fragment shader: " << m_programSimple->log();
|
||||
}
|
||||
if (!m_programSimple->link()) {
|
||||
qDebug() << "GLShaderSpectrogram::initializeGL: error linking shader: " << m_programSimple->log();
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initGrid(int elements)
|
||||
{
|
||||
m_gridElements = std::min(elements, 4096); // Limit to keep memory requirements realistic
|
||||
qDebug() << "GLShaderSpectrogram::initGrid: requested: " << elements << " actual: " << m_gridElements;
|
||||
int e1 = m_gridElements+1;
|
||||
|
||||
// Grid vertices
|
||||
std::vector<QVector2D> vertices(e1 * e1);
|
||||
|
||||
for (int i = 0; i < e1; i++)
|
||||
{
|
||||
for (int j = 0; j < e1; j++)
|
||||
{
|
||||
vertices[i*e1+j].setX(j / (float)m_gridElements);
|
||||
vertices[i*e1+j].setY(i / (float)m_gridElements);
|
||||
}
|
||||
}
|
||||
|
||||
m_vertexBuf.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
m_vertexBuf.create();
|
||||
|
||||
m_index0Buf.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
m_index0Buf.create();
|
||||
|
||||
m_index1Buf.setUsagePattern(QOpenGLBuffer::StaticDraw);
|
||||
m_index1Buf.create();
|
||||
|
||||
m_vertexBuf.bind();
|
||||
m_vertexBuf.allocate(&vertices[0], vertices.size() * sizeof(QVector2D));
|
||||
|
||||
// Create an array of indices into the vertex array that traces both horizontal and vertical lines
|
||||
std::vector<GLuint> indices(m_gridElements * m_gridElements * 6);
|
||||
int i = 0;
|
||||
|
||||
for (int y = 0; y < e1; y++) {
|
||||
for (int x = 0; x < m_gridElements; x++) {
|
||||
indices[i++] = y * (e1) + x;
|
||||
indices[i++] = y * (e1) + x + 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < e1; x++) {
|
||||
for (int y = 0; y < m_gridElements; y++) {
|
||||
indices[i++] = y * (e1) + x;
|
||||
indices[i++] = (y + 1) * (e1) + x;
|
||||
}
|
||||
}
|
||||
|
||||
m_index0Buf.bind();
|
||||
m_index0Buf.allocate(&indices[0], m_gridElements * (e1) * 4 * sizeof(GLuint));
|
||||
|
||||
// Create another array of indices that describes all the triangles needed to create a completely filled surface
|
||||
i = 0;
|
||||
|
||||
for (int y = 0; y < m_gridElements; y++) {
|
||||
for (int x = 0; x < m_gridElements; x++) {
|
||||
indices[i++] = y * (e1) + x;
|
||||
indices[i++] = y * (e1) + x + 1;
|
||||
indices[i++] = (y + 1) * (e1) + x + 1;
|
||||
|
||||
indices[i++] = y * (e1) + x;
|
||||
indices[i++] = (y + 1) * (e1) + x + 1;
|
||||
indices[i++] = (y + 1) * (e1) + x;
|
||||
}
|
||||
}
|
||||
|
||||
m_index1Buf.bind();
|
||||
m_index1Buf.allocate(&indices[0], indices.size() * sizeof(GLuint));
|
||||
|
||||
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
|
||||
f->glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initColorMapTexture(const QString &colorMapName)
|
||||
{
|
||||
if (m_useImmutableStorage) {
|
||||
initColorMapTextureImmutable(colorMapName);
|
||||
} else {
|
||||
initColorMapTextureMutable(colorMapName);
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initColorMapTextureImmutable(const QString &colorMapName)
|
||||
{
|
||||
if (!m_colorMapTexture)
|
||||
{
|
||||
m_colorMapTexture = new QOpenGLTexture(QOpenGLTexture::Target1D);
|
||||
m_colorMapTexture->setFormat(QOpenGLTexture::RGB32F);
|
||||
m_colorMapTexture->setSize(256);
|
||||
m_colorMapTexture->allocateStorage();
|
||||
m_colorMapTexture->setMinificationFilter(QOpenGLTexture::Linear);
|
||||
m_colorMapTexture->setMagnificationFilter(QOpenGLTexture::Linear);
|
||||
m_colorMapTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
|
||||
}
|
||||
|
||||
GLfloat *colorMap = (GLfloat *)ColorMap::getColorMap(colorMapName);
|
||||
if (colorMap) {
|
||||
m_colorMapTexture->setData(QOpenGLTexture::RGB, QOpenGLTexture::Float32, colorMap);
|
||||
} else {
|
||||
qDebug() << "GLShaderSpectrogram::initColorMapTextureImmutable: colorMap " << colorMapName << " not supported";
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initColorMapTextureMutable(const QString &colorMapName)
|
||||
{
|
||||
if (m_colorMapTextureId)
|
||||
{
|
||||
glDeleteTextures(1, &m_colorMapTextureId);
|
||||
m_colorMapTextureId = 0;
|
||||
}
|
||||
|
||||
glGenTextures(1, &m_colorMapTextureId);
|
||||
glBindTexture(GL_TEXTURE_1D, m_colorMapTextureId);
|
||||
GLfloat *colorMap = (GLfloat *)ColorMap::getColorMap(colorMapName);
|
||||
if (colorMap) {
|
||||
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 256, 0, GL_RGB, GL_FLOAT, colorMap);
|
||||
} else {
|
||||
qDebug() << "GLShaderSpectrogram::initColorMapTextureMutable: colorMap " << colorMapName << " not supported";
|
||||
}
|
||||
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, QOpenGLTexture::Repeat);
|
||||
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, QOpenGLTexture::Repeat);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initTexture(const QImage& image)
|
||||
{
|
||||
if (m_useImmutableStorage) {
|
||||
initTextureImmutable(image);
|
||||
} else {
|
||||
initTextureMutable(image);
|
||||
}
|
||||
initGrid(image.width());
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initTextureImmutable(const QImage& image)
|
||||
{
|
||||
if (m_texture) {
|
||||
delete m_texture;
|
||||
}
|
||||
|
||||
m_texture = new QOpenGLTexture(image);
|
||||
|
||||
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
|
||||
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
|
||||
m_texture->setWrapMode(QOpenGLTexture::Repeat);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::initTextureMutable(const QImage& image)
|
||||
{
|
||||
if (m_textureId)
|
||||
{
|
||||
glDeleteTextures(1, &m_textureId);
|
||||
m_textureId = 0;
|
||||
}
|
||||
|
||||
glGenTextures(1, &m_textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textureId);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
|
||||
image.width(), image.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image.constScanLine(0));
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, QOpenGLTexture::Repeat);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, QOpenGLTexture::Repeat);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::subTexture(int xOffset, int yOffset, int width, int height, const void *pixels)
|
||||
{
|
||||
if (m_useImmutableStorage) {
|
||||
subTextureImmutable(xOffset, yOffset, width, height, pixels);
|
||||
} else {
|
||||
subTextureMutable(xOffset, yOffset, width, height, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels)
|
||||
{
|
||||
if (!m_texture)
|
||||
{
|
||||
qDebug("GLShaderSpectrogram::subTextureImmutable: no texture defined. Doing nothing");
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
|
||||
m_texture->bind();
|
||||
f->glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels)
|
||||
{
|
||||
if (!m_textureId)
|
||||
{
|
||||
qDebug("GLShaderSpectrogram::subTextureMutable: no texture defined. Doing nothing");
|
||||
return;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_textureId);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::drawSurface(SpectrumSettings::SpectrogramStyle style, float textureOffset, bool invert)
|
||||
{
|
||||
if ((m_useImmutableStorage && !m_texture) || (!m_useImmutableStorage && !m_textureId))
|
||||
{
|
||||
qDebug("GLShaderSpectrogram::drawSurface: no texture defined. Doing nothing");
|
||||
return;
|
||||
}
|
||||
|
||||
QOpenGLShaderProgram *program;
|
||||
if (style == SpectrumSettings::Shaded) {
|
||||
program = m_programShaded;
|
||||
} else {
|
||||
program = m_programSimple;
|
||||
}
|
||||
|
||||
// Note that translation to the origin and rotation
|
||||
// needs to be performed in reverse order to what you
|
||||
// might normally expect
|
||||
// See: https://bugreports.qt.io/browse/QTBUG-20752
|
||||
|
||||
QMatrix4x4 vertexTransform;
|
||||
vertexTransform.translate(0.0f, 0.0f, -1.65f);
|
||||
applyScaleRotate(vertexTransform);
|
||||
vertexTransform.translate(-0.5f, -0.5f, 0.0f);
|
||||
applyPerspective(vertexTransform);
|
||||
|
||||
float rot = invert ? 1.0 : -1.0;
|
||||
QMatrix4x4 textureTransform(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, rot, 0.0, textureOffset,
|
||||
0.0, 0.0, rot, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0); // Use this to move texture for each row of data
|
||||
|
||||
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
|
||||
program->bind();
|
||||
|
||||
if (program != m_programForLocs)
|
||||
{
|
||||
m_textureTransformLoc = program->uniformLocation("textureTransform");
|
||||
m_vertexTransformLoc = program->uniformLocation("vertexTransform");
|
||||
m_textureLoc = program->uniformLocation("texture");
|
||||
m_limitLoc = program->uniformLocation("limit");
|
||||
m_brightnessLoc = program->uniformLocation("brightness");
|
||||
m_colorMapLoc = program->uniformLocation("colorMap");
|
||||
m_lightDirLoc = program->uniformLocation("lightDir");
|
||||
m_lightPosLoc = program->uniformLocation("lightPos");
|
||||
m_programForLocs = program;
|
||||
}
|
||||
|
||||
program->setUniformValue(m_vertexTransformLoc, vertexTransform);
|
||||
program->setUniformValue(m_textureTransformLoc, textureTransform);
|
||||
|
||||
if (m_useImmutableStorage)
|
||||
{
|
||||
m_texture->bind();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
m_colorMapTexture->bind();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
else
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, m_textureId);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_1D, m_colorMapTextureId);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
program->setUniformValue(m_textureLoc, 0); // set uniform to texture unit?
|
||||
program->setUniformValue(m_colorMapLoc, 1);
|
||||
|
||||
program->setUniformValue(m_limitLoc, 1.5f*1.0f/(float)(m_gridElements));
|
||||
|
||||
if (style == SpectrumSettings::Outline)
|
||||
{
|
||||
// When drawing the outline, slightly darken the triangles
|
||||
// so grid is a bit more visible
|
||||
program->setUniformValue(m_brightnessLoc, 0.85f);
|
||||
}
|
||||
else
|
||||
{
|
||||
program->setUniformValue(m_brightnessLoc, 1.0f);
|
||||
}
|
||||
|
||||
if (style == SpectrumSettings::Shaded)
|
||||
{
|
||||
QMatrix4x4 matrix;
|
||||
matrix.rotate(m_lightRotX, 1.0f, 0.0f, 0.0f);
|
||||
matrix.rotate(m_lightRotY, 0.0f, 1.0f, 0.0f);
|
||||
matrix.rotate(m_lightRotZ, 0.0f, 0.0f, 1.0f);
|
||||
QVector3D vector = matrix * QVector3D(0.0f, 0.0f, -1.0f);
|
||||
GLfloat lightDir[3] = {vector.x(), vector.y(), vector.z()};
|
||||
GLfloat lightPos[3] = {m_lightTranslateX, m_lightTranslateY, m_lightTranslateZ};
|
||||
program->setUniformValueArray(m_lightDirLoc, lightDir, 1, 3);
|
||||
program->setUniformValueArray(m_lightPosLoc, lightPos, 1, 3);
|
||||
}
|
||||
|
||||
f->glEnable(GL_DEPTH_TEST);
|
||||
|
||||
f->glPolygonOffset(1, 0);
|
||||
f->glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
|
||||
int attribute_coord2d = program->attributeLocation("coord2d");
|
||||
f->glEnableVertexAttribArray(attribute_coord2d);
|
||||
m_vertexBuf.bind();
|
||||
f->glVertexAttribPointer(attribute_coord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
m_index1Buf.bind();
|
||||
switch (style)
|
||||
{
|
||||
case SpectrumSettings::Points:
|
||||
f->glDrawElements(GL_POINTS, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
|
||||
break;
|
||||
case SpectrumSettings::Lines:
|
||||
f->glDrawElements(GL_LINES, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
|
||||
break;
|
||||
case SpectrumSettings::Solid:
|
||||
case SpectrumSettings::Outline:
|
||||
case SpectrumSettings::Shaded:
|
||||
f->glDrawElements(GL_TRIANGLES, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
f->glPolygonOffset(0, 0);
|
||||
f->glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
|
||||
if (style == SpectrumSettings::Outline)
|
||||
{
|
||||
// Draw the outline
|
||||
program->setUniformValue(m_brightnessLoc, 1.5f);
|
||||
m_index0Buf.bind();
|
||||
f->glDrawElements(GL_LINES, m_gridElements * (m_gridElements+1) * 4, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
f->glDisableVertexAttribArray(attribute_coord2d);
|
||||
// Need to do this, otherwise nothing else is drawn...
|
||||
f->glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
// If this is left enabled, channel markers aren't drawn properly
|
||||
f->glDisable(GL_DEPTH_TEST);
|
||||
|
||||
program->release();
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::cleanup()
|
||||
{
|
||||
if (!QOpenGLContext::currentContext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_programShaded)
|
||||
{
|
||||
delete m_programShaded;
|
||||
m_programShaded = nullptr;
|
||||
}
|
||||
|
||||
if (m_programSimple)
|
||||
{
|
||||
delete m_programSimple;
|
||||
m_programSimple = nullptr;
|
||||
}
|
||||
|
||||
m_programForLocs = nullptr;
|
||||
|
||||
if (m_texture)
|
||||
{
|
||||
delete m_texture;
|
||||
m_texture = nullptr;
|
||||
}
|
||||
|
||||
if (m_textureId)
|
||||
{
|
||||
glDeleteTextures(1, &m_textureId);
|
||||
m_textureId = 0;
|
||||
}
|
||||
|
||||
if (m_colorMapTextureId)
|
||||
{
|
||||
glDeleteTextures(1, &m_colorMapTextureId);
|
||||
m_colorMapTextureId = 0;
|
||||
}
|
||||
|
||||
if (m_colorMapTexture)
|
||||
{
|
||||
delete m_colorMapTexture;
|
||||
m_colorMapTexture = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool GLShaderSpectrogram::useImmutableStorage()
|
||||
{
|
||||
QOpenGLContext* ctx = QOpenGLContext::currentContext();
|
||||
QSurfaceFormat sf = ctx->format();
|
||||
|
||||
if (sf.version() >= qMakePair(4, 2)
|
||||
|| ctx->hasExtension(QByteArrayLiteral("GL_ARB_texture_storage"))
|
||||
|| ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_storage")))
|
||||
{
|
||||
void (QOPENGLF_APIENTRYP glTexStorage2D)(
|
||||
GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height);
|
||||
glTexStorage2D = reinterpret_cast<void (QOPENGLF_APIENTRYP)(
|
||||
GLenum, GLsizei, GLenum, GLsizei, GLsizei)>(ctx->getProcAddress("glTexStorage2D"));
|
||||
int data = 0;
|
||||
GLuint textureId;
|
||||
glGenTextures(1, &textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, &data);
|
||||
GLenum err = glGetError();
|
||||
glDeleteTextures(1, &textureId);
|
||||
return err == GL_NO_ERROR;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::translateX(float distance)
|
||||
{
|
||||
m_translateX += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::translateY(float distance)
|
||||
{
|
||||
m_translateY += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::translateZ(float distance)
|
||||
{
|
||||
m_translateZ += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::rotateX(float degrees)
|
||||
{
|
||||
m_rotX += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::rotateY(float degrees)
|
||||
{
|
||||
m_rotY += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::rotateZ(float degrees)
|
||||
{
|
||||
m_rotZ += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::setScaleX(float factor)
|
||||
{
|
||||
m_scaleX = factor;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::setScaleY(float factor)
|
||||
{
|
||||
m_scaleY = factor;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::setScaleZ(float factor)
|
||||
{
|
||||
m_scaleZ = factor;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::userScaleZ(float factor)
|
||||
{
|
||||
m_userScaleZ *= factor;
|
||||
if ((m_userScaleZ < 0.1) && (factor < 1.0)) {
|
||||
m_userScaleZ = 0.0;
|
||||
} else if ((m_userScaleZ == 0.0) && (factor > 1.0)) {
|
||||
m_userScaleZ = 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::reset()
|
||||
{
|
||||
// Don't reset m_scaleX, m_scaleY and m_scaleZ, m_aspectRatio
|
||||
// as they are not set directly by user, but depend on window size
|
||||
m_translateX = 0.0f;
|
||||
m_translateY = 0.0f;
|
||||
m_translateZ = 0.0f;
|
||||
m_rotX = -45.0f;
|
||||
m_rotY = 0.0f;
|
||||
m_rotZ = 0.0f;
|
||||
m_userScaleZ = 1.0f;
|
||||
m_verticalAngle = 45.0f;
|
||||
m_lightTranslateX = 0.0f;
|
||||
m_lightTranslateY = 0.0f;
|
||||
m_lightTranslateZ = 0.0f;
|
||||
m_lightRotX = 0.0f;
|
||||
m_lightRotY = 0.0f;
|
||||
m_lightRotZ = 0.0f;
|
||||
setPerspective();
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::setAspectRatio(float aspectRatio)
|
||||
{
|
||||
m_aspectRatio = aspectRatio;
|
||||
setPerspective();
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::verticalAngle(float degrees)
|
||||
{
|
||||
m_verticalAngle += degrees;
|
||||
m_verticalAngle = std::max(1.0f, m_verticalAngle);
|
||||
m_verticalAngle = std::min(179.0f, m_verticalAngle);
|
||||
setPerspective();
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::setPerspective()
|
||||
{
|
||||
m_perspective.setToIdentity();
|
||||
m_perspective.perspective(m_verticalAngle, m_aspectRatio, 0.1, 7.0);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightTranslateX(float distance)
|
||||
{
|
||||
m_lightTranslateX += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightTranslateY(float distance)
|
||||
{
|
||||
m_lightTranslateY += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightTranslateZ(float distance)
|
||||
{
|
||||
m_lightTranslateZ += distance;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightRotateX(float degrees)
|
||||
{
|
||||
m_lightRotX += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightRotateY(float degrees)
|
||||
{
|
||||
m_lightRotY += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::lightRotateZ(float degrees)
|
||||
{
|
||||
m_lightRotZ += degrees;
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::applyScaleRotate(QMatrix4x4 &matrix)
|
||||
{
|
||||
// As above, this is in reverse
|
||||
matrix.translate(m_translateX, m_translateY, m_translateZ);
|
||||
matrix.rotate(m_rotX, 1.0f, 0.0f, 0.0f);
|
||||
matrix.rotate(m_rotY, 0.0f, 1.0f, 0.0f);
|
||||
matrix.rotate(m_rotZ, 0.0f, 0.0f, 1.0f);
|
||||
matrix.scale(m_scaleX, m_scaleY, m_scaleZ * m_userScaleZ);
|
||||
}
|
||||
|
||||
void GLShaderSpectrogram::applyPerspective(QMatrix4x4 &matrix)
|
||||
{
|
||||
matrix = m_perspective * matrix;
|
||||
}
|
||||
|
||||
// The clamp is to prevent old data affecting new data (And vice versa),
|
||||
// which can happen where the texture repeats - might be a better way to do it
|
||||
const QString GLShaderSpectrogram::m_vertexShader = QString(
|
||||
"attribute vec2 coord2d;\n"
|
||||
"varying vec4 coord;\n"
|
||||
"varying float lightDistance;\n"
|
||||
"uniform mat4 textureTransform;\n"
|
||||
"uniform mat4 vertexTransform;\n"
|
||||
"uniform sampler2D texture;\n"
|
||||
"uniform float limit;\n"
|
||||
"uniform vec3 lightPos;\n"
|
||||
"void main(void) {\n"
|
||||
" coord = textureTransform * vec4(clamp(coord2d, limit, 1.0-limit), 0, 1);\n"
|
||||
" coord.z = (texture2D(texture, coord.xy).r);\n"
|
||||
" gl_Position = vertexTransform * vec4(coord2d, coord.z, 1);\n"
|
||||
" lightDistance = length(lightPos - gl_Position.xyz);\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
// We need to use a geometry shader to calculate the normals, as they are only
|
||||
// determined after z has been calculated for the verticies in the vertex shader
|
||||
const QString GLShaderSpectrogram::m_geometryShader = QString(
|
||||
"#version 330\n"
|
||||
"layout(triangles) in;\n"
|
||||
"layout(triangle_strip, max_vertices=3) out;\n"
|
||||
"in vec4 coord[];\n"
|
||||
"in float lightDistance[];\n"
|
||||
"out vec4 coord2;\n"
|
||||
"out vec3 normal;\n"
|
||||
"out float lightDistance2;\n"
|
||||
"void main(void) {\n"
|
||||
" vec3 a = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz;\n"
|
||||
" vec3 b = (gl_in[2].gl_Position - gl_in[0].gl_Position).xyz;\n"
|
||||
" vec3 N = normalize(cross(b, a));\n"
|
||||
" for(int i=0; i < gl_in.length(); ++i)\n"
|
||||
" {\n"
|
||||
" gl_Position = gl_in[i].gl_Position;\n"
|
||||
" normal = N;\n"
|
||||
" coord2 = coord[i];\n"
|
||||
" lightDistance2 = lightDistance[i];\n"
|
||||
" EmitVertex( );\n"
|
||||
" }\n"
|
||||
" EndPrimitive( );\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
const QString GLShaderSpectrogram::m_fragmentShaderShaded = QString(
|
||||
"varying vec4 coord2;\n"
|
||||
"varying vec3 normal;\n"
|
||||
"varying float lightDistance2;\n"
|
||||
"uniform sampler1D colorMap;\n"
|
||||
"uniform vec3 lightDir;\n"
|
||||
"void main(void) {\n"
|
||||
" float factor;\n"
|
||||
" if (gl_FrontFacing)\n"
|
||||
" factor = 1.0;\n"
|
||||
" else\n"
|
||||
" factor = 0.5;\n"
|
||||
" float ambient = 0.4;\n"
|
||||
" vec3 color;\n"
|
||||
" color.r = texture1D(colorMap, coord2.z).r;\n"
|
||||
" color.g = texture1D(colorMap, coord2.z).g;\n"
|
||||
" color.b = texture1D(colorMap, coord2.z).b;\n"
|
||||
" float cosTheta = max(0.0, dot(normal, lightDir));\n"
|
||||
" float d2 = max(1.0, lightDistance2*lightDistance2);\n"
|
||||
" vec3 relection = (ambient * color + color * cosTheta / d2) * factor;\n"
|
||||
" gl_FragColor[0] = relection.r;\n"
|
||||
" gl_FragColor[1] = relection.g;\n"
|
||||
" gl_FragColor[2] = relection.b;\n"
|
||||
" gl_FragColor[3] = 1.0;\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
const QString GLShaderSpectrogram::m_fragmentShaderSimple = QString(
|
||||
"varying vec4 coord;\n"
|
||||
"uniform float brightness;\n"
|
||||
"uniform sampler1D colorMap;\n"
|
||||
"void main(void) {\n"
|
||||
" float factor;\n"
|
||||
" if (gl_FrontFacing)\n"
|
||||
" factor = 1.0;\n"
|
||||
" else\n"
|
||||
" factor = 0.5;\n"
|
||||
" gl_FragColor[0] = texture1D(colorMap, coord.z).r * brightness * factor;\n"
|
||||
" gl_FragColor[1] = texture1D(colorMap, coord.z).g * brightness * factor;\n"
|
||||
" gl_FragColor[2] = texture1D(colorMap, coord.z).b * brightness * factor;\n"
|
||||
" gl_FragColor[3] = 1.0;\n"
|
||||
"}\n"
|
||||
);
|
128
sdrgui/gui/glshaderspectrogram.h
Normal file
128
sdrgui/gui/glshaderspectrogram.h
Normal file
@ -0,0 +1,128 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 Edouard Griffiths, F4EXB. //
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_GUI_GLSHADERSPECTROGRAM_H_
|
||||
#define INCLUDE_GUI_GLSHADERSPECTROGRAM_H_
|
||||
|
||||
#include <QString>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
#include "export.h"
|
||||
#include "dsp/spectrumsettings.h"
|
||||
|
||||
class QOpenGLShaderProgram;
|
||||
class QMatrix4x4;
|
||||
class QImage;
|
||||
|
||||
class SDRGUI_API GLShaderSpectrogram : protected QOpenGLFunctions
|
||||
{
|
||||
public:
|
||||
GLShaderSpectrogram();
|
||||
~GLShaderSpectrogram();
|
||||
|
||||
void initializeGL();
|
||||
void initColorMapTexture(const QString &colorMapName);
|
||||
void initTexture(const QImage& image);
|
||||
void subTexture(int xOffset, int yOffset, int width, int height, const void *pixels);
|
||||
void drawSurface(SpectrumSettings::SpectrogramStyle style, float textureOffset, bool invert);
|
||||
void cleanup();
|
||||
void translateX(float distance);
|
||||
void translateY(float distance);
|
||||
void translateZ(float distance);
|
||||
void rotateX(float degrees);
|
||||
void rotateY(float degrees);
|
||||
void rotateZ(float degrees);
|
||||
void setScaleX(float factor);
|
||||
void setScaleY(float factor);
|
||||
void setScaleZ(float factor);
|
||||
void userScaleZ(float factor);
|
||||
void reset();
|
||||
void setAspectRatio(float aspectRatio);
|
||||
void verticalAngle(float degrees);
|
||||
void lightTranslateX(float distance);
|
||||
void lightTranslateY(float distance);
|
||||
void lightTranslateZ(float distance);
|
||||
void lightRotateX(float degrees);
|
||||
void lightRotateY(float degrees);
|
||||
void lightRotateZ(float degrees);
|
||||
void applyScaleRotate(QMatrix4x4 &matrix);
|
||||
void applyPerspective(QMatrix4x4 &matrix);
|
||||
|
||||
private:
|
||||
void initColorMapTextureMutable(const QString &colorMapName);
|
||||
void initColorMapTextureImmutable(const QString &colorMapName);
|
||||
void initTextureImmutable(const QImage& image);
|
||||
void subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels);
|
||||
void initTextureMutable(const QImage& image);
|
||||
void subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels);
|
||||
bool useImmutableStorage();
|
||||
void initGrid(int elements);
|
||||
void setPerspective();
|
||||
|
||||
QOpenGLShaderProgram *m_programShaded;
|
||||
QOpenGLShaderProgram *m_programSimple;
|
||||
QOpenGLTexture *m_texture;
|
||||
unsigned int m_textureId;
|
||||
QOpenGLTexture *m_colorMapTexture;
|
||||
unsigned int m_colorMapTextureId;
|
||||
|
||||
QOpenGLShaderProgram *m_programForLocs; // Which program the locations are for
|
||||
int m_textureTransformLoc;
|
||||
int m_vertexTransformLoc;
|
||||
int m_textureLoc;
|
||||
int m_limitLoc;
|
||||
int m_brightnessLoc;
|
||||
int m_colorMapLoc;
|
||||
int m_lightDirLoc;
|
||||
int m_lightPosLoc;
|
||||
|
||||
bool m_useImmutableStorage;
|
||||
static const QString m_vertexShader;
|
||||
static const QString m_geometryShader;
|
||||
static const QString m_fragmentShaderShaded;
|
||||
static const QString m_fragmentShaderSimple;
|
||||
|
||||
QOpenGLBuffer m_vertexBuf;
|
||||
QOpenGLBuffer m_index0Buf;
|
||||
QOpenGLBuffer m_index1Buf;
|
||||
|
||||
float m_translateX;
|
||||
float m_translateY;
|
||||
float m_translateZ;
|
||||
float m_rotX;
|
||||
float m_rotY;
|
||||
float m_rotZ;
|
||||
float m_scaleX;
|
||||
float m_scaleY;
|
||||
float m_scaleZ;
|
||||
float m_userScaleZ;
|
||||
float m_verticalAngle;
|
||||
float m_aspectRatio;
|
||||
float m_lightTranslateX;
|
||||
float m_lightTranslateY;
|
||||
float m_lightTranslateZ;
|
||||
float m_lightRotX;
|
||||
float m_lightRotY;
|
||||
float m_lightRotZ;
|
||||
QMatrix4x4 m_perspective;
|
||||
int m_gridElements;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_GUI_GLSHADERSPECTROGRAM_H_ */
|
@ -144,16 +144,16 @@ void GLShaderTextured::subTextureMutable(int xOffset, int yOffset, int width, in
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
}
|
||||
|
||||
void GLShaderTextured::drawSurface(const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderTextured::drawSurface(const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
if (m_useImmutableStorage) {
|
||||
draw(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices);
|
||||
draw(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices, nbComponents);
|
||||
} else {
|
||||
drawMutable(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices);
|
||||
drawMutable(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices, nbComponents);
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
if (!m_texture)
|
||||
{
|
||||
@ -167,7 +167,7 @@ void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix
|
||||
m_texture->bind();
|
||||
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
|
||||
f->glEnableVertexAttribArray(0); // vertex
|
||||
f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
f->glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
f->glEnableVertexAttribArray(1); // texture coordinates
|
||||
f->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
|
||||
f->glDrawArrays(mode, 0, nbVertices);
|
||||
@ -175,7 +175,7 @@ void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix
|
||||
m_program->release();
|
||||
}
|
||||
|
||||
void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
|
||||
void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
|
||||
{
|
||||
if (!m_textureId)
|
||||
{
|
||||
@ -188,7 +188,7 @@ void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transfor
|
||||
glBindTexture(GL_TEXTURE_2D, m_textureId);
|
||||
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
|
||||
glEnableVertexAttribArray(0); // vertex
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
|
||||
glEnableVertexAttribArray(1); // texture coordinates
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
|
||||
glDrawArrays(mode, 0, nbVertices);
|
||||
|
@ -41,12 +41,12 @@ public:
|
||||
void initializeGL();
|
||||
void initTexture(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);
|
||||
void subTexture(int xOffset, int yOffset, int width, int height, const void *pixels);
|
||||
void drawSurface(const QMatrix4x4& transformMatrix, GLfloat* textureCoords, GLfloat *vertices, int nbVertices);
|
||||
void drawSurface(const QMatrix4x4& transformMatrix, GLfloat* textureCoords, GLfloat *vertices, int nbVertices, int nbComponents=2);
|
||||
void cleanup();
|
||||
|
||||
private:
|
||||
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices);
|
||||
void drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices);
|
||||
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents);
|
||||
void drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents);
|
||||
void initTextureImmutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);
|
||||
void subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels);
|
||||
void initTextureMutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);
|
||||
|
@ -23,8 +23,10 @@
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QPainter>
|
||||
#include <QFontDatabase>
|
||||
#include "maincore.h"
|
||||
#include "dsp/spectrumvis.h"
|
||||
#include "gui/glspectrum.h"
|
||||
#include "settings/mainsettings.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/db.h"
|
||||
|
||||
@ -79,6 +81,16 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
|
||||
m_displayWaterfall(true),
|
||||
m_ssbSpectrum(false),
|
||||
m_lsbDisplay(false),
|
||||
m_3DSpectrogramBuffer(nullptr),
|
||||
m_3DSpectrogramBufferPos(0),
|
||||
m_3DSpectrogramTextureHeight(-1),
|
||||
m_3DSpectrogramTexturePos(0),
|
||||
m_display3DSpectrogram(false),
|
||||
m_rotate3DSpectrogram(false),
|
||||
m_pan3DSpectrogram(false),
|
||||
m_scaleZ3DSpectrogram(false),
|
||||
m_3DSpectrogramStyle(SpectrumSettings::Outline),
|
||||
m_3DSpectrogramColorMap("Angel"),
|
||||
m_histogramBuffer(nullptr),
|
||||
m_histogram(nullptr),
|
||||
m_displayHistogram(true),
|
||||
@ -91,8 +103,18 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
|
||||
m_calibrationGain(1.0),
|
||||
m_calibrationShiftdB(0.0),
|
||||
m_calibrationInterpMode(SpectrumSettings::CalibInterpLinear),
|
||||
m_messageQueueToGUI(nullptr)
|
||||
m_messageQueueToGUI(nullptr),
|
||||
m_openGLLogger(nullptr)
|
||||
{
|
||||
// Enable multisampling anti-aliasing (MSAA)
|
||||
int multisamples = MainCore::instance()->getSettings().getMultisampling();
|
||||
if (multisamples > 0)
|
||||
{
|
||||
QSurfaceFormat format;
|
||||
format.setSamples(multisamples);
|
||||
setFormat(format);
|
||||
}
|
||||
|
||||
setObjectName("GLSpectrum");
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
@ -179,6 +201,10 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
|
||||
m_timer.setTimerType(Qt::PreciseTimer);
|
||||
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
|
||||
m_timer.start(m_fpsPeriodMs);
|
||||
|
||||
// Handle KeyEvents
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
installEventFilter(this);
|
||||
}
|
||||
|
||||
GLSpectrum::~GLSpectrum()
|
||||
@ -191,6 +217,12 @@ GLSpectrum::~GLSpectrum()
|
||||
m_waterfallBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (m_3DSpectrogramBuffer)
|
||||
{
|
||||
delete m_3DSpectrogramBuffer;
|
||||
m_3DSpectrogramBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (m_histogramBuffer)
|
||||
{
|
||||
delete m_histogramBuffer;
|
||||
@ -202,6 +234,12 @@ GLSpectrum::~GLSpectrum()
|
||||
delete[] m_histogram;
|
||||
m_histogram = nullptr;
|
||||
}
|
||||
|
||||
if (m_openGLLogger)
|
||||
{
|
||||
delete m_openGLLogger;
|
||||
m_openGLLogger = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrum::setCenterFrequency(qint64 frequency)
|
||||
@ -296,6 +334,31 @@ void GLSpectrum::setDisplayWaterfall(bool display)
|
||||
update();
|
||||
}
|
||||
|
||||
void GLSpectrum::setDisplay3DSpectrogram(bool display)
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_display3DSpectrogram = display;
|
||||
m_changesPending = true;
|
||||
stopDrag();
|
||||
m_mutex.unlock();
|
||||
update();
|
||||
}
|
||||
|
||||
void GLSpectrum::set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style)
|
||||
{
|
||||
m_3DSpectrogramStyle = style;
|
||||
update();
|
||||
}
|
||||
|
||||
void GLSpectrum::set3DSpectrogramColorMap(const QString &colorMap)
|
||||
{
|
||||
m_mutex.lock();
|
||||
m_3DSpectrogramColorMap = colorMap;
|
||||
m_changesPending = true;
|
||||
m_mutex.unlock();
|
||||
update();
|
||||
}
|
||||
|
||||
void GLSpectrum::setSsbSpectrum(bool ssbSpectrum)
|
||||
{
|
||||
m_ssbSpectrum = ssbSpectrum;
|
||||
@ -537,6 +600,7 @@ void GLSpectrum::newSpectrum(const Real *spectrum, int nbBins, int fftSize)
|
||||
}
|
||||
|
||||
updateWaterfall(spectrum);
|
||||
update3DSpectrogram(spectrum);
|
||||
updateHistogram(spectrum);
|
||||
}
|
||||
|
||||
@ -563,6 +627,29 @@ void GLSpectrum::updateWaterfall(const Real *spectrum)
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrum::update3DSpectrogram(const Real *spectrum)
|
||||
{
|
||||
if (m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer->height())
|
||||
{
|
||||
quint8* pix = (quint8*)m_3DSpectrogramBuffer->scanLine(m_3DSpectrogramBufferPos);
|
||||
|
||||
for (int i = 0; i < m_nbBins; i++)
|
||||
{
|
||||
int v = (int)((spectrum[i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
|
||||
|
||||
if (v > 255) {
|
||||
v = 255;
|
||||
} else if (v < 0) {
|
||||
v = 0;
|
||||
}
|
||||
|
||||
*pix++ = v;
|
||||
}
|
||||
|
||||
m_3DSpectrogramBufferPos++;
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrum::updateHistogram(const Real *spectrum)
|
||||
{
|
||||
quint8* b = m_histogram;
|
||||
@ -691,6 +778,27 @@ void GLSpectrum::initializeGL()
|
||||
else {
|
||||
qDebug() << "GLSpectrum::initializeGL: current context is invalid";
|
||||
}
|
||||
|
||||
if ( (MainCore::instance()->getSettings().getConsoleMinLogLevel() <= QtDebugMsg)
|
||||
|| (MainCore::instance()->getSettings().getFileMinLogLevel() <= QtDebugMsg))
|
||||
{
|
||||
// Enable OpenGL debugging
|
||||
QSurfaceFormat format = glCurrentContext->format();
|
||||
format.setOption(QSurfaceFormat::DebugContext);
|
||||
glCurrentContext->setFormat(format);
|
||||
|
||||
if (glCurrentContext->hasExtension(QByteArrayLiteral("GL_KHR_debug")))
|
||||
{
|
||||
m_openGLLogger = new QOpenGLDebugLogger(this);
|
||||
m_openGLLogger->initialize();
|
||||
connect(m_openGLLogger, &QOpenGLDebugLogger::messageLogged, this, &GLSpectrum::openGLDebug);
|
||||
m_openGLLogger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GLSpectrum::initializeGL: GL_KHR_debug not available";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -711,6 +819,14 @@ void GLSpectrum::initializeGL()
|
||||
m_glShaderHistogram.initializeGL();
|
||||
m_glShaderTextOverlay.initializeGL();
|
||||
m_glShaderInfo.initializeGL();
|
||||
m_glShaderSpectrogram.initializeGL();
|
||||
m_glShaderSpectrogramTimeScale.initializeGL();
|
||||
m_glShaderSpectrogramPowerScale.initializeGL();
|
||||
}
|
||||
|
||||
void GLSpectrum::openGLDebug(const QOpenGLDebugMessage &debugMessage)
|
||||
{
|
||||
qDebug() << "GLSpectrum::openGLDebug: " << debugMessage;
|
||||
}
|
||||
|
||||
void GLSpectrum::resizeGL(int width, int height)
|
||||
@ -753,11 +869,43 @@ void GLSpectrum::paintGL()
|
||||
|
||||
QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
|
||||
glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glFunctions->glClear(GL_COLOR_BUFFER_BIT);
|
||||
glFunctions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// paint waterfall
|
||||
if (m_displayWaterfall)
|
||||
QMatrix4x4 spectrogramGridMatrix;
|
||||
spectrogramGridMatrix.translate(0.0f, 0.0f, -1.65f);
|
||||
m_glShaderSpectrogram.applyScaleRotate(spectrogramGridMatrix);
|
||||
spectrogramGridMatrix.translate(-0.5f, -0.5f, 0.0f);
|
||||
m_glShaderSpectrogram.applyPerspective(spectrogramGridMatrix);
|
||||
|
||||
if (m_display3DSpectrogram)
|
||||
{
|
||||
// paint 3D spectrogram
|
||||
if (m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos < m_3DSpectrogramTextureHeight)
|
||||
{
|
||||
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, m_3DSpectrogramBufferPos, m_3DSpectrogramBuffer->scanLine(0));
|
||||
m_3DSpectrogramTexturePos += m_3DSpectrogramBufferPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
int breakLine = m_3DSpectrogramTextureHeight - m_3DSpectrogramTexturePos;
|
||||
int linesLeft = m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos - m_3DSpectrogramTextureHeight;
|
||||
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, breakLine, m_3DSpectrogramBuffer->scanLine(0));
|
||||
m_glShaderSpectrogram.subTexture(0, 0, m_nbBins, linesLeft, m_3DSpectrogramBuffer->scanLine(breakLine));
|
||||
m_3DSpectrogramTexturePos = linesLeft;
|
||||
}
|
||||
|
||||
m_3DSpectrogramBufferPos = 0;
|
||||
|
||||
float prop_y = m_3DSpectrogramTexturePos / (m_3DSpectrogramTextureHeight - 1.0);
|
||||
|
||||
// Temporarily reduce viewport to waterfall area so anything outside is clipped
|
||||
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
|
||||
m_glShaderSpectrogram.drawSurface(m_3DSpectrogramStyle, prop_y, m_invertedWaterfall);
|
||||
glFunctions->glViewport(0, 0, width(), height());
|
||||
}
|
||||
else if (m_displayWaterfall)
|
||||
{
|
||||
// paint 2D waterfall
|
||||
{
|
||||
GLfloat vtx1[] = {
|
||||
0, m_invertedWaterfall ? 0.0f : 1.0f,
|
||||
@ -960,7 +1108,7 @@ void GLSpectrum::paintGL()
|
||||
}
|
||||
|
||||
// paint frequency scale
|
||||
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram )
|
||||
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram)
|
||||
{
|
||||
{
|
||||
GLfloat vtx1[] = {
|
||||
@ -1015,6 +1163,74 @@ void GLSpectrum::paintGL()
|
||||
}
|
||||
}
|
||||
|
||||
// paint 3D spectrogram scales
|
||||
if (m_display3DSpectrogram && m_displayGrid)
|
||||
{
|
||||
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
|
||||
{
|
||||
float l = m_spectrogramTimePixmap.width() / (float) width();
|
||||
float r = m_rightMargin / (float) width();
|
||||
float h = m_frequencyPixmap.height() / (float) m_waterfallHeight;
|
||||
|
||||
GLfloat vtx1[] = {
|
||||
-l, -h,
|
||||
1+r, -h,
|
||||
1+r, 0,
|
||||
-l, 0
|
||||
};
|
||||
GLfloat tex1[] = {
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0,
|
||||
0, 0
|
||||
};
|
||||
|
||||
m_glShaderFrequencyScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
|
||||
}
|
||||
|
||||
{
|
||||
float w = m_spectrogramTimePixmap.width() / (float) width();
|
||||
float h = (m_bottomMargin/2) / (float) m_waterfallHeight; // m_bottomMargin is fm.ascent
|
||||
|
||||
GLfloat vtx1[] = {
|
||||
-w, 0.0-h,
|
||||
0, 0.0-h,
|
||||
0, 1.0+h,
|
||||
-w, 1.0+h
|
||||
};
|
||||
GLfloat tex1[] = {
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0,
|
||||
0, 0
|
||||
};
|
||||
|
||||
m_glShaderSpectrogramTimeScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
|
||||
}
|
||||
|
||||
{
|
||||
float w = m_spectrogramPowerPixmap.width() / (float) width();
|
||||
float h = m_topMargin / (float) m_spectrogramPowerPixmap.height();
|
||||
|
||||
GLfloat vtx1[] = {
|
||||
-w, 1.0, 0.0,
|
||||
0, 1.0, 0.0,
|
||||
0, 1.0, 1.0+h,
|
||||
-w, 1.0, 1.0+h,
|
||||
};
|
||||
GLfloat tex1[] = {
|
||||
0, 1,
|
||||
1, 1,
|
||||
1, 0,
|
||||
0, 0
|
||||
};
|
||||
|
||||
m_glShaderSpectrogramPowerScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4, 3);
|
||||
}
|
||||
|
||||
glFunctions->glViewport(0, 0, width(), height());
|
||||
}
|
||||
|
||||
// paint max hold lines on top of histogram
|
||||
if (m_displayMaxHold)
|
||||
{
|
||||
@ -1154,6 +1370,128 @@ void GLSpectrum::paintGL()
|
||||
}
|
||||
}
|
||||
|
||||
// paint 3D spectrogram grid - this is drawn on top of signal, so that appears slightly transparent
|
||||
// x-axis is freq, y time and z power
|
||||
if (m_displayGrid && m_display3DSpectrogram)
|
||||
{
|
||||
const ScaleEngine::TickList* tickList;
|
||||
const ScaleEngine::Tick* tick;
|
||||
|
||||
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
|
||||
|
||||
tickList = &m_powerScale.getTickList();
|
||||
{
|
||||
GLfloat *q3 = m_q3TickPower.m_array;
|
||||
int effectiveTicks = 0;
|
||||
|
||||
for (int i= 0; i < tickList->count(); i++)
|
||||
{
|
||||
tick = &(*tickList)[i];
|
||||
|
||||
if (tick->major)
|
||||
{
|
||||
if (tick->textSize > 0)
|
||||
{
|
||||
float y = tick->pos / m_powerScale.getSize();
|
||||
q3[6*effectiveTicks] = 0.0;
|
||||
q3[6*effectiveTicks+1] = 1.0;
|
||||
q3[6*effectiveTicks+2] = y;
|
||||
q3[6*effectiveTicks+3] = 1.0;
|
||||
q3[6*effectiveTicks+4] = 1.0;
|
||||
q3[6*effectiveTicks+5] = y;
|
||||
effectiveTicks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
|
||||
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
|
||||
}
|
||||
|
||||
tickList = &m_timeScale.getTickList();
|
||||
{
|
||||
GLfloat *q3 = m_q3TickTime.m_array;
|
||||
int effectiveTicks = 0;
|
||||
|
||||
for (int i= 0; i < tickList->count(); i++)
|
||||
{
|
||||
tick = &(*tickList)[i];
|
||||
|
||||
if (tick->major)
|
||||
{
|
||||
if (tick->textSize > 0)
|
||||
{
|
||||
float y = tick->pos / m_timeScale.getSize();
|
||||
q3[4*effectiveTicks] = 0.0;
|
||||
q3[4*effectiveTicks+1] = 1.0 - y;
|
||||
q3[4*effectiveTicks+2] = 1.0;
|
||||
q3[4*effectiveTicks+3] = 1.0 - y;
|
||||
effectiveTicks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
|
||||
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
|
||||
}
|
||||
|
||||
tickList = &m_frequencyScale.getTickList();
|
||||
{
|
||||
GLfloat *q3 = m_q3TickFrequency.m_array;
|
||||
int effectiveTicks = 0;
|
||||
|
||||
for (int i= 0; i < tickList->count(); i++)
|
||||
{
|
||||
tick = &(*tickList)[i];
|
||||
|
||||
if (tick->major)
|
||||
{
|
||||
if (tick->textSize > 0)
|
||||
{
|
||||
float x = tick->pos / m_frequencyScale.getSize();
|
||||
q3[4*effectiveTicks] = x;
|
||||
q3[4*effectiveTicks+1] = -0.0;
|
||||
q3[4*effectiveTicks+2] = x;
|
||||
q3[4*effectiveTicks+3] = 1.0;
|
||||
effectiveTicks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
|
||||
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
|
||||
}
|
||||
{
|
||||
GLfloat *q3 = m_q3TickFrequency.m_array;
|
||||
int effectiveTicks = 0;
|
||||
|
||||
for (int i= 0; i < tickList->count(); i++)
|
||||
{
|
||||
tick = &(*tickList)[i];
|
||||
|
||||
if (tick->major)
|
||||
{
|
||||
if (tick->textSize > 0)
|
||||
{
|
||||
float x = tick->pos / m_frequencyScale.getSize();
|
||||
q3[6*effectiveTicks] = x;
|
||||
q3[6*effectiveTicks+1] = 1.0;
|
||||
q3[6*effectiveTicks+2] = 0.0;
|
||||
q3[6*effectiveTicks+3] = x;
|
||||
q3[6*effectiveTicks+4] = 1.0;
|
||||
q3[6*effectiveTicks+5] = 1.0;
|
||||
effectiveTicks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
|
||||
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
|
||||
}
|
||||
|
||||
glFunctions->glViewport(0, 0, width(), height());
|
||||
}
|
||||
|
||||
// paint histogram grid
|
||||
if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid))
|
||||
{
|
||||
@ -1566,7 +1904,7 @@ void GLSpectrum::applyChanges()
|
||||
m_rightMargin = fm.horizontalAdvance("000");
|
||||
|
||||
// displays both histogram and waterfall
|
||||
if (m_displayWaterfall && (m_displayHistogram | m_displayMaxHold | m_displayCurrent))
|
||||
if ((m_displayWaterfall || m_display3DSpectrogram) && (m_displayHistogram | m_displayMaxHold | m_displayCurrent))
|
||||
{
|
||||
m_waterfallHeight = height() * m_waterfallShare - 1;
|
||||
|
||||
@ -1611,24 +1949,9 @@ void GLSpectrum::applyChanges()
|
||||
m_timeScale.setRange(Unit::Time, 0, 1);
|
||||
}
|
||||
|
||||
m_powerScale.setSize(m_histogramHeight);
|
||||
|
||||
if (m_linear)
|
||||
{
|
||||
Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
|
||||
m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
|
||||
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
|
||||
}
|
||||
|
||||
m_leftMargin = m_timeScale.getScaleWidth();
|
||||
|
||||
if (m_powerScale.getScaleWidth() > m_leftMargin) {
|
||||
m_leftMargin = m_powerScale.getScaleWidth();
|
||||
}
|
||||
setPowerScale(m_histogramHeight);
|
||||
|
||||
m_leftMargin += 2 * M;
|
||||
|
||||
@ -1688,8 +2011,8 @@ void GLSpectrum::applyChanges()
|
||||
-2.0f
|
||||
);
|
||||
}
|
||||
// displays waterfall only
|
||||
else if (m_displayWaterfall)
|
||||
// displays waterfall/3D spectrogram only
|
||||
else if (m_displayWaterfall || m_display3DSpectrogram)
|
||||
{
|
||||
m_histogramHeight = 0;
|
||||
histogramTop = 0;
|
||||
@ -1725,6 +2048,9 @@ void GLSpectrum::applyChanges()
|
||||
}
|
||||
|
||||
m_leftMargin = m_timeScale.getScaleWidth();
|
||||
|
||||
setPowerScale((height() - m_topMargin - m_bottomMargin) / 2.0);
|
||||
|
||||
m_leftMargin += 2 * M;
|
||||
|
||||
setFrequencyScale();
|
||||
@ -1772,10 +2098,10 @@ void GLSpectrum::applyChanges()
|
||||
m_waterfallHeight = 0;
|
||||
m_histogramHeight = height() - m_topMargin - m_frequencyScaleHeight;
|
||||
|
||||
m_powerScale.setSize(m_histogramHeight);
|
||||
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
|
||||
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
|
||||
m_leftMargin = m_powerScale.getScaleWidth();
|
||||
m_leftMargin = 0;
|
||||
|
||||
setPowerScale(m_histogramHeight);
|
||||
|
||||
m_leftMargin += 2 * M;
|
||||
|
||||
setFrequencyScale();
|
||||
@ -1830,6 +2156,9 @@ void GLSpectrum::applyChanges()
|
||||
m_waterfallHeight = 0;
|
||||
}
|
||||
|
||||
m_glShaderSpectrogram.setScaleX(((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight));
|
||||
m_glShaderSpectrogram.setScaleZ((m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4) / (float)(width() - m_leftMargin - m_rightMargin));
|
||||
|
||||
// bounding boxes
|
||||
m_frequencyScaleRect = QRect(
|
||||
0,
|
||||
@ -1876,6 +2205,13 @@ void GLSpectrum::applyChanges()
|
||||
);
|
||||
}
|
||||
|
||||
m_glShaderSpectrogram.setAspectRatio((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight);
|
||||
|
||||
m_3DSpectrogramBottom = m_bottomMargin;
|
||||
if (!m_invertedWaterfall) {
|
||||
m_3DSpectrogramBottom += m_histogramHeight + m_frequencyScaleHeight + 1;
|
||||
}
|
||||
|
||||
// channel overlays
|
||||
int64_t centerFrequency;
|
||||
int frequencySpan;
|
||||
@ -2034,7 +2370,7 @@ void GLSpectrum::applyChanges()
|
||||
// prepare left scales (time and power)
|
||||
{
|
||||
m_leftMarginPixmap = QPixmap(m_leftMargin - 1, height());
|
||||
m_leftMarginPixmap.fill(Qt::black);
|
||||
m_leftMarginPixmap.fill(Qt::transparent);
|
||||
{
|
||||
QPainter painter(&m_leftMarginPixmap);
|
||||
painter.setPen(QColor(0xf0, 0xf0, 0xff));
|
||||
@ -2066,7 +2402,7 @@ void GLSpectrum::applyChanges()
|
||||
m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage());
|
||||
}
|
||||
// prepare frequency scale
|
||||
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent){
|
||||
if (m_displayWaterfall || m_display3DSpectrogram || m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
|
||||
m_frequencyPixmap = QPixmap(width(), m_frequencyScaleHeight);
|
||||
m_frequencyPixmap.fill(Qt::transparent);
|
||||
{
|
||||
@ -2135,6 +2471,55 @@ void GLSpectrum::applyChanges()
|
||||
|
||||
m_glShaderFrequencyScale.initTexture(m_frequencyPixmap.toImage());
|
||||
}
|
||||
// prepare left scale for spectrogram (time)
|
||||
{
|
||||
m_spectrogramTimePixmap = QPixmap(m_leftMargin - 1, fm.ascent() + m_waterfallHeight);
|
||||
m_spectrogramTimePixmap.fill(Qt::transparent);
|
||||
{
|
||||
QPainter painter(&m_spectrogramTimePixmap);
|
||||
painter.setPen(QColor(0xf0, 0xf0, 0xff));
|
||||
painter.setFont(font());
|
||||
const ScaleEngine::TickList* tickList;
|
||||
const ScaleEngine::Tick* tick;
|
||||
if (m_display3DSpectrogram) {
|
||||
tickList = &m_timeScale.getTickList();
|
||||
for (int i = 0; i < tickList->count(); i++) {
|
||||
tick = &(*tickList)[i];
|
||||
if (tick->major) {
|
||||
if (tick->textSize > 0)
|
||||
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, fm.height() + tick->textPos), tick->text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_glShaderSpectrogramTimeScale.initTexture(m_spectrogramTimePixmap.toImage());
|
||||
}
|
||||
// prepare vertical scale for spectrogram (power)
|
||||
{
|
||||
int h = m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4;
|
||||
m_spectrogramPowerPixmap = QPixmap(m_leftMargin - 1, m_topMargin + h);
|
||||
m_spectrogramPowerPixmap.fill(Qt::transparent);
|
||||
{
|
||||
QPainter painter(&m_spectrogramPowerPixmap);
|
||||
painter.setPen(QColor(0xf0, 0xf0, 0xff));
|
||||
painter.setFont(font());
|
||||
const ScaleEngine::TickList* tickList;
|
||||
const ScaleEngine::Tick* tick;
|
||||
if (m_display3DSpectrogram) {
|
||||
tickList = &m_powerScale.getTickList();
|
||||
for (int i = 0; i < tickList->count(); i++) {
|
||||
tick = &(*tickList)[i];
|
||||
if (tick->major) {
|
||||
if (tick->textSize > 0)
|
||||
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + h - tick->textPos - 1), tick->text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_glShaderSpectrogramPowerScale.initTexture(m_spectrogramPowerPixmap.toImage());
|
||||
}
|
||||
|
||||
// Top info line
|
||||
m_glInfoBoxMatrix.setToIdentity();
|
||||
@ -2188,7 +2573,18 @@ void GLSpectrum::applyChanges()
|
||||
m_waterfallBuffer->fill(qRgb(0x00, 0x00, 0x00));
|
||||
m_glShaderWaterfall.initTexture(*m_waterfallBuffer);
|
||||
m_waterfallBufferPos = 0;
|
||||
|
||||
if (m_3DSpectrogramBuffer) {
|
||||
delete m_3DSpectrogramBuffer;
|
||||
}
|
||||
|
||||
m_3DSpectrogramBuffer = new QImage(m_nbBins, m_waterfallHeight, QImage::Format_Grayscale8);
|
||||
|
||||
m_3DSpectrogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
|
||||
m_glShaderSpectrogram.initTexture(*m_3DSpectrogramBuffer);
|
||||
m_3DSpectrogramBufferPos = 0;
|
||||
}
|
||||
m_glShaderSpectrogram.initColorMapTexture(m_3DSpectrogramColorMap);
|
||||
|
||||
if (fftSizeChanged)
|
||||
{
|
||||
@ -2218,11 +2614,13 @@ void GLSpectrum::applyChanges()
|
||||
{
|
||||
m_waterfallTextureHeight = m_waterfallHeight;
|
||||
m_waterfallTexturePos = 0;
|
||||
m_3DSpectrogramTextureHeight = m_waterfallHeight;
|
||||
m_3DSpectrogramTexturePos = 0;
|
||||
}
|
||||
|
||||
m_q3TickTime.allocate(4*m_timeScale.getTickList().count());
|
||||
m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count());
|
||||
m_q3TickPower.allocate(4*m_powerScale.getTickList().count());
|
||||
m_q3TickPower.allocate(6*m_powerScale.getTickList().count()); // 6 as we need 3d points for 3D spectrogram
|
||||
updateHistogramMarkers();
|
||||
updateWaterfallMarkers();
|
||||
updateSortedAnnotationMarkers();
|
||||
@ -2461,6 +2859,37 @@ void GLSpectrum::updateCalibrationPoints()
|
||||
|
||||
void GLSpectrum::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (m_rotate3DSpectrogram)
|
||||
{
|
||||
// Rotate 3D Spectrogram
|
||||
QPointF delta = m_mousePrevLocalPos - event->localPos();
|
||||
m_mousePrevLocalPos = event->localPos();
|
||||
m_glShaderSpectrogram.rotateZ(-delta.x()/2.0f);
|
||||
m_glShaderSpectrogram.rotateX(-delta.y()/2.0f);
|
||||
repaint(); // Force repaint in case acquisition is stopped
|
||||
return;
|
||||
}
|
||||
if (m_pan3DSpectrogram)
|
||||
{
|
||||
// Pan 3D Spectrogram
|
||||
QPointF delta = m_mousePrevLocalPos - event->localPos();
|
||||
m_mousePrevLocalPos = event->localPos();
|
||||
m_glShaderSpectrogram.translateX(-delta.x()/2.0f/500.0f);
|
||||
m_glShaderSpectrogram.translateY(delta.y()/2.0f/500.0f);
|
||||
repaint(); // Force repaint in case acquisition is stopped
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_scaleZ3DSpectrogram)
|
||||
{
|
||||
// Scale 3D Spectrogram in Z dimension
|
||||
QPointF delta = m_mousePrevLocalPos - event->localPos();
|
||||
m_mousePrevLocalPos = event->localPos();
|
||||
m_glShaderSpectrogram.userScaleZ(1.0+(float)delta.y()/20.0);
|
||||
repaint(); // Force repaint in case acquisition is stopped
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
|
||||
{
|
||||
if (m_frequencyScaleRect.contains(event->pos()))
|
||||
@ -2573,6 +3002,20 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
const QPointF& ep = event->localPos();
|
||||
|
||||
if ((event->button() == Qt::MiddleButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
|
||||
{
|
||||
m_pan3DSpectrogram = true;
|
||||
m_mousePrevLocalPos = ep;
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event->button() == Qt::RightButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
|
||||
{
|
||||
m_scaleZ3DSpectrogram = true;
|
||||
m_mousePrevLocalPos = ep;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton)
|
||||
{
|
||||
QPointF pHis = ep;
|
||||
@ -2681,7 +3124,7 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
|
||||
frequency = m_frequencyScale.getRangeMin() + pWat.x()*m_frequencyScale.getRange();
|
||||
float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange();
|
||||
|
||||
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
|
||||
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1) && !m_display3DSpectrogram)
|
||||
{
|
||||
if (m_waterfallMarkers.size() < SpectrumWaterfallMarker::m_maxNbOfMarkers)
|
||||
{
|
||||
@ -2727,6 +3170,16 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
frequencyPan(event);
|
||||
}
|
||||
else if (m_display3DSpectrogram)
|
||||
{
|
||||
// Detect click and drag to rotate 3D spectrogram
|
||||
if (pointInWaterfallOrSpectrogram(ep))
|
||||
{
|
||||
m_rotate3DSpectrogram = true;
|
||||
m_mousePrevLocalPos = ep;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_markersDisplay == SpectrumSettings::MarkersDisplayAnnotations) &&
|
||||
(ep.y() <= m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f))
|
||||
@ -2794,6 +3247,9 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
|
||||
|
||||
void GLSpectrum::mouseReleaseEvent(QMouseEvent*)
|
||||
{
|
||||
m_pan3DSpectrogram = false;
|
||||
m_rotate3DSpectrogram = false;
|
||||
m_scaleZ3DSpectrogram = false;
|
||||
if (m_cursorState == CSSplitterMoving)
|
||||
{
|
||||
releaseMouse();
|
||||
@ -2808,12 +3264,32 @@ void GLSpectrum::mouseReleaseEvent(QMouseEvent*)
|
||||
|
||||
void GLSpectrum::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
channelMarkerMove(event, 100);
|
||||
} else if (event->modifiers() & Qt::ControlModifier) {
|
||||
channelMarkerMove(event, 10);
|
||||
} else {
|
||||
channelMarkerMove(event, 1);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
const QPointF& ep = event->position();
|
||||
#else
|
||||
const QPointF& ep = event->pos();
|
||||
#endif
|
||||
if (pointInWaterfallOrSpectrogram(ep))
|
||||
{
|
||||
// Scale 3D spectrogram when mouse wheel moved
|
||||
// Some mice use delta in steps of 120 for 15 degrees
|
||||
// for one step of mouse wheel
|
||||
// Other mice/trackpads use smaller values
|
||||
int delta = event->angleDelta().y();
|
||||
if (delta != 0) {
|
||||
m_glShaderSpectrogram.verticalAngle(-5.0*delta/120.0);
|
||||
}
|
||||
repaint(); // Force repaint in case acquisition is stopped
|
||||
}
|
||||
else
|
||||
{
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
channelMarkerMove(event, 100);
|
||||
} else if (event->modifiers() & Qt::ControlModifier) {
|
||||
channelMarkerMove(event, 10);
|
||||
} else {
|
||||
channelMarkerMove(event, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2973,6 +3449,26 @@ void GLSpectrum::setFrequencyScale()
|
||||
m_frequencyScale.setMakeOpposite(m_lsbDisplay);
|
||||
}
|
||||
|
||||
void GLSpectrum::setPowerScale(int height)
|
||||
{
|
||||
m_powerScale.setSize(height);
|
||||
|
||||
if (m_linear)
|
||||
{
|
||||
Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
|
||||
m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
|
||||
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
|
||||
}
|
||||
|
||||
if (m_powerScale.getScaleWidth() > m_leftMargin) {
|
||||
m_leftMargin = m_powerScale.getScaleWidth();
|
||||
}
|
||||
}
|
||||
|
||||
void GLSpectrum::getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan)
|
||||
{
|
||||
frequencySpan = (m_frequencyZoomFactor == 1) ?
|
||||
@ -3033,6 +3529,17 @@ void GLSpectrum::channelMarkerMove(QWheelEvent *event, int mul)
|
||||
zoom(event);
|
||||
}
|
||||
|
||||
// Return if specified point is within the bounds of the waterfall / 3D spectrogram screen area
|
||||
bool GLSpectrum::pointInWaterfallOrSpectrogram(const QPointF &point) const
|
||||
{
|
||||
// m_waterfallRect is normalised to [0,1]
|
||||
QPointF pWat = point;
|
||||
pWat.rx() = (point.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
|
||||
pWat.ry() = (point.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
|
||||
|
||||
return (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1);
|
||||
}
|
||||
|
||||
void GLSpectrum::enterEvent(QEvent* event)
|
||||
{
|
||||
m_mouseInside = true;
|
||||
@ -3109,6 +3616,9 @@ void GLSpectrum::cleanup()
|
||||
m_glShaderWaterfall.cleanup();
|
||||
m_glShaderTextOverlay.cleanup();
|
||||
m_glShaderInfo.cleanup();
|
||||
m_glShaderSpectrogram.cleanup();
|
||||
m_glShaderSpectrogramTimeScale.cleanup();
|
||||
m_glShaderSpectrogramPowerScale.cleanup();
|
||||
//doneCurrent();
|
||||
}
|
||||
|
||||
@ -3268,3 +3778,88 @@ void GLSpectrum::formatTextInfo(QString& info)
|
||||
info.append(tr("SP:%1 ").arg(displayScaled(frequencySpan, 'f', 3, true)));
|
||||
}
|
||||
}
|
||||
|
||||
bool GLSpectrum::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress)
|
||||
{
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
m_glShaderSpectrogram.lightRotateX(-5.0f);
|
||||
} else if (keyEvent->modifiers() & Qt::AltModifier) {
|
||||
m_glShaderSpectrogram.lightTranslateY(0.05);
|
||||
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.translateY(0.05);
|
||||
} else {
|
||||
m_glShaderSpectrogram.rotateX(-5.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Down:
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
m_glShaderSpectrogram.lightRotateX(5.0f);
|
||||
} else if (keyEvent->modifiers() & Qt::AltModifier) {
|
||||
m_glShaderSpectrogram.lightTranslateY(-0.05);
|
||||
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.translateY(-0.05);
|
||||
} else {
|
||||
m_glShaderSpectrogram.rotateX(5.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Left:
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
m_glShaderSpectrogram.lightRotateZ(5.0f);
|
||||
} else if (keyEvent->modifiers() & Qt::AltModifier) {
|
||||
m_glShaderSpectrogram.lightTranslateX(-0.05);
|
||||
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.translateX(-0.05);
|
||||
} else {
|
||||
m_glShaderSpectrogram.rotateZ(5.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Right:
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
m_glShaderSpectrogram.lightRotateZ(-5.0f);
|
||||
} else if (keyEvent->modifiers() & Qt::AltModifier) {
|
||||
m_glShaderSpectrogram.lightTranslateX(0.05);
|
||||
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.translateX(0.05);
|
||||
} else {
|
||||
m_glShaderSpectrogram.rotateZ(-5.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Plus:
|
||||
if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.userScaleZ(1.1f);
|
||||
} else {
|
||||
m_glShaderSpectrogram.verticalAngle(-1.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Minus:
|
||||
if (keyEvent->modifiers() & Qt::ControlModifier) {
|
||||
m_glShaderSpectrogram.userScaleZ(0.9f);
|
||||
} else {
|
||||
m_glShaderSpectrogram.verticalAngle(1.0f);
|
||||
}
|
||||
break;
|
||||
case Qt::Key_R:
|
||||
m_glShaderSpectrogram.reset();
|
||||
break;
|
||||
case Qt::Key_F:
|
||||
// Project straight down and scale to view, so it's a bit like 2D
|
||||
m_glShaderSpectrogram.reset();
|
||||
m_glShaderSpectrogram.rotateX(45.0f);
|
||||
m_glShaderSpectrogram.verticalAngle(-9.0f);
|
||||
m_glShaderSpectrogram.userScaleZ(0.0f);
|
||||
break;
|
||||
}
|
||||
repaint(); // Force repaint in case acquisition is stopped
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return QOpenGLWidget::eventFilter(object, event);
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,14 @@
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QMatrix4x4>
|
||||
#include <QPoint>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QOpenGLDebugLogger>
|
||||
#include "gui/scaleengine.h"
|
||||
#include "gui/glshadersimple.h"
|
||||
#include "gui/glshadertextured.h"
|
||||
#include "dsp/glspectruminterface.h"
|
||||
#include "gui/glshaderspectrogram.h"
|
||||
#include "dsp/spectrummarkers.h"
|
||||
#include "dsp/channelmarker.h"
|
||||
#include "dsp/spectrumsettings.h"
|
||||
@ -42,6 +45,7 @@
|
||||
class QOpenGLShaderProgram;
|
||||
class MessageQueue;
|
||||
class SpectrumVis;
|
||||
class QOpenGLDebugLogger;
|
||||
|
||||
class SDRGUI_API GLSpectrum : public QOpenGLWidget, public GLSpectrumInterface {
|
||||
Q_OBJECT
|
||||
@ -140,6 +144,9 @@ public:
|
||||
void setDecayDivisor(int decayDivisor);
|
||||
void setHistoStroke(int stroke);
|
||||
void setDisplayWaterfall(bool display);
|
||||
void setDisplay3DSpectrogram(bool display);
|
||||
void set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style);
|
||||
void set3DSpectrogramColorMap(const QString &colorMap);
|
||||
void setSsbSpectrum(bool ssbSpectrum);
|
||||
void setLsbDisplay(bool lsbDisplay);
|
||||
void setInvertedWaterfall(bool inv);
|
||||
@ -292,6 +299,21 @@ private:
|
||||
bool m_ssbSpectrum;
|
||||
bool m_lsbDisplay;
|
||||
|
||||
QImage* m_3DSpectrogramBuffer;
|
||||
int m_3DSpectrogramBufferPos;
|
||||
int m_3DSpectrogramTextureHeight;
|
||||
int m_3DSpectrogramTexturePos;
|
||||
bool m_display3DSpectrogram;
|
||||
bool m_rotate3DSpectrogram; //!< Set when mouse is pressed in 3D spectrogram area for rotation when mouse is moved
|
||||
bool m_pan3DSpectrogram;
|
||||
bool m_scaleZ3DSpectrogram;
|
||||
QPointF m_mousePrevLocalPos; //!< Position of the mouse for last event
|
||||
int m_3DSpectrogramBottom;
|
||||
QPixmap m_spectrogramTimePixmap;
|
||||
QPixmap m_spectrogramPowerPixmap;
|
||||
SpectrumSettings::SpectrogramStyle m_3DSpectrogramStyle;
|
||||
QString m_3DSpectrogramColorMap;
|
||||
|
||||
QRgb m_histogramPalette[240];
|
||||
QImage* m_histogramBuffer;
|
||||
quint8* m_histogram; //!< Spectrum phosphor matrix of FFT width and PSD height scaled to 100. values [0..239]
|
||||
@ -316,6 +338,9 @@ private:
|
||||
GLShaderTextured m_glShaderHistogram;
|
||||
GLShaderTextured m_glShaderTextOverlay;
|
||||
GLShaderTextured m_glShaderInfo;
|
||||
GLShaderSpectrogram m_glShaderSpectrogram;
|
||||
GLShaderTextured m_glShaderSpectrogramTimeScale;
|
||||
GLShaderTextured m_glShaderSpectrogramPowerScale;
|
||||
int m_matrixLoc;
|
||||
int m_colorLoc;
|
||||
bool m_useCalibration;
|
||||
@ -328,8 +353,10 @@ private:
|
||||
IncrementalArray<GLfloat> m_q3FFT;
|
||||
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
QOpenGLDebugLogger *m_openGLLogger;
|
||||
|
||||
void updateWaterfall(const Real *spectrum);
|
||||
void update3DSpectrogram(const Real *spectrum);
|
||||
void updateHistogram(const Real *spectrum);
|
||||
|
||||
void initializeGL();
|
||||
@ -354,7 +381,9 @@ private:
|
||||
void resetFrequencyZoom();
|
||||
void updateFFTLimits();
|
||||
void setFrequencyScale();
|
||||
void setPowerScale(int height);
|
||||
void getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan);
|
||||
bool pointInWaterfallOrSpectrogram(const QPointF &point) const;
|
||||
|
||||
void enterEvent(QEvent* event);
|
||||
void leaveEvent(QEvent* event);
|
||||
@ -394,6 +423,8 @@ private slots:
|
||||
void tick();
|
||||
void channelMarkerChanged();
|
||||
void channelMarkerDestroyed(QObject* object);
|
||||
void openGLDebug(const QOpenGLDebugMessage &debugMessage);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_GLSPECTRUM_H
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "gui/spectrummarkersdialog.h"
|
||||
#include "gui/spectrumcalibrationpointsdialog.h"
|
||||
#include "gui/flowlayout.h"
|
||||
#include "util/colormap.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "util/db.h"
|
||||
#include "ui_glspectrumgui.h"
|
||||
@ -70,6 +71,9 @@ GLSpectrumGUI::GLSpectrumGUI(QWidget* parent) :
|
||||
ui->levelRange->setStyleSheet(levelStyle);
|
||||
ui->fftOverlap->setStyleSheet(levelStyle);
|
||||
|
||||
ui->spectrogramColorMap->addItems(ColorMap::getColorMapNames());
|
||||
ui->spectrogramColorMap->setCurrentText("Angel");
|
||||
|
||||
connect(&m_messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
|
||||
CRightClickEnabler *wsSpectrumRightClickEnabler = new CRightClickEnabler(ui->wsSpectrum);
|
||||
@ -154,6 +158,11 @@ void GLSpectrumGUI::displaySettings()
|
||||
ui->decayDivisor->setSliderPosition(m_settings.m_decayDivisor);
|
||||
ui->stroke->setSliderPosition(m_settings.m_histogramStroke);
|
||||
ui->waterfall->setChecked(m_settings.m_displayWaterfall);
|
||||
ui->spectrogram->setChecked(m_settings.m_display3DSpectrogram);
|
||||
ui->spectrogramStyle->setCurrentIndex((int) m_settings.m_3DSpectrogramStyle);
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram);
|
||||
ui->spectrogramColorMap->setCurrentText(m_settings.m_3DSpectrogramColorMap);
|
||||
ui->spectrogramColorMap->setVisible(m_settings.m_display3DSpectrogram);
|
||||
ui->maxHold->setChecked(m_settings.m_displayMaxHold);
|
||||
ui->current->setChecked(m_settings.m_displayCurrent);
|
||||
ui->histogram->setChecked(m_settings.m_displayHistogram);
|
||||
@ -235,6 +244,9 @@ void GLSpectrumGUI::applySettings()
|
||||
void GLSpectrumGUI::applySpectrumSettings()
|
||||
{
|
||||
m_glSpectrum->setDisplayWaterfall(m_settings.m_displayWaterfall);
|
||||
m_glSpectrum->setDisplay3DSpectrogram(m_settings.m_display3DSpectrogram);
|
||||
m_glSpectrum->set3DSpectrogramStyle(m_settings.m_3DSpectrogramStyle);
|
||||
m_glSpectrum->set3DSpectrogramColorMap(m_settings.m_3DSpectrogramColorMap);
|
||||
m_glSpectrum->setInvertedWaterfall(m_settings.m_invertedWaterfall);
|
||||
m_glSpectrum->setDisplayMaxHold(m_settings.m_displayMaxHold);
|
||||
m_glSpectrum->setDisplayCurrent(m_settings.m_displayCurrent);
|
||||
@ -451,9 +463,42 @@ void GLSpectrumGUI::on_stroke_valueChanged(int index)
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_spectrogramStyle_currentIndexChanged(int index)
|
||||
{
|
||||
m_settings.m_3DSpectrogramStyle = (SpectrumSettings::SpectrogramStyle)index;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_spectrogramColorMap_currentIndexChanged(int index)
|
||||
{
|
||||
(void) index;
|
||||
m_settings.m_3DSpectrogramColorMap = ui->spectrogramColorMap->currentText();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_waterfall_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_displayWaterfall = checked;
|
||||
if (checked)
|
||||
{
|
||||
blockApplySettings(true);
|
||||
ui->spectrogram->setChecked(false);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void GLSpectrumGUI::on_spectrogram_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_display3DSpectrogram = checked;
|
||||
if (checked)
|
||||
{
|
||||
blockApplySettings(true);
|
||||
ui->waterfall->setChecked(false);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
ui->spectrogramStyle->setVisible(m_settings.m_display3DSpectrogram);
|
||||
ui->spectrogramColorMap->setVisible(m_settings.m_display3DSpectrogram);
|
||||
applySettings();
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,8 @@ private slots:
|
||||
void on_decay_valueChanged(int index);
|
||||
void on_decayDivisor_valueChanged(int index);
|
||||
void on_stroke_valueChanged(int index);
|
||||
void on_spectrogramStyle_currentIndexChanged(int index);
|
||||
void on_spectrogramColorMap_currentIndexChanged(int index);
|
||||
void on_gridIntensity_valueChanged(int index);
|
||||
void on_traceIntensity_valueChanged(int index);
|
||||
void on_averagingMode_currentIndexChanged(int index);
|
||||
@ -105,6 +107,7 @@ private slots:
|
||||
void on_markers_clicked(bool checked);
|
||||
|
||||
void on_waterfall_toggled(bool checked);
|
||||
void on_spectrogram_toggled(bool checked);
|
||||
void on_histogram_toggled(bool checked);
|
||||
void on_maxHold_toggled(bool checked);
|
||||
void on_current_toggled(bool checked);
|
||||
|
@ -123,13 +123,13 @@
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>45</width>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>45</width>
|
||||
<width>50</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -479,7 +479,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Spectrum maximum FPS (NL for No Limit)</string>
|
||||
<string>Spectrum maximum FPS</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@ -794,6 +794,95 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="spectrogram">
|
||||
<property name="toolTip">
|
||||
<string>Display 3D spectrogram</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3D Spectrogram</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../resources/res.qrc">
|
||||
<normaloff>:/3dspectrogram.png</normaloff>:/3dspectrogram.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="spectrogramStyle">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>3D Spectrogram Style</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Points</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Lines</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Solid</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Outline</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Shaded</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="spectrogramColorMap">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>3D Spectrogram Color Map</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="grid">
|
||||
<property name="toolTip">
|
||||
|
46
sdrgui/gui/graphicsdialog.cpp
Normal file
46
sdrgui/gui/graphicsdialog.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "graphicsdialog.h"
|
||||
#include "ui_graphicsdialog.h"
|
||||
|
||||
GraphicsDialog::GraphicsDialog(MainSettings& mainSettings, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::GraphicsDialog),
|
||||
m_mainSettings(mainSettings)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
int samples = m_mainSettings.getMultisampling();
|
||||
if (samples == 0) {
|
||||
ui->multisampling->setCurrentText("Off");
|
||||
} else {
|
||||
ui->multisampling->setCurrentText(QString::number(samples));
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsDialog::~GraphicsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void GraphicsDialog::accept()
|
||||
{
|
||||
m_mainSettings.setMultisampling(ui->multisampling->currentText().toInt());
|
||||
QDialog::accept();
|
||||
}
|
43
sdrgui/gui/graphicsdialog.h
Normal file
43
sdrgui/gui/graphicsdialog.h
Normal file
@ -0,0 +1,43 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SDRGUI_GUI_GRAPHICS_H_
|
||||
#define SDRGUI_GUI_GRAPHICS_H_
|
||||
|
||||
#include <QDialog>
|
||||
#include "settings/mainsettings.h"
|
||||
#include "export.h"
|
||||
|
||||
namespace Ui {
|
||||
class GraphicsDialog;
|
||||
}
|
||||
|
||||
class SDRGUI_API GraphicsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GraphicsDialog(MainSettings& mainSettings, QWidget* parent = 0);
|
||||
~GraphicsDialog();
|
||||
|
||||
private:
|
||||
Ui::GraphicsDialog* ui;
|
||||
MainSettings& m_mainSettings;
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
};
|
||||
|
||||
#endif /* SDRGUI_GUI_GRAPHICS_H_ */
|
128
sdrgui/gui/graphicsdialog.ui
Normal file
128
sdrgui/gui/graphicsdialog.ui
Normal file
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GraphicsDialog</class>
|
||||
<widget class="QDialog" name="GraphicsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>277</width>
|
||||
<height>98</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Graphics settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="multisamplingLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Multisampling (MSAA)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="multisampling">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of samples to use for mulitsampling anti-aliasing (MSAA) - Requires windows to be reopened</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Off</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>GraphicsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>257</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>GraphicsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>314</x>
|
||||
<y>194</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>203</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -315,3 +315,39 @@ Use the toggle button to switch between relative and calibrated power readings.
|
||||
|
||||
Right click to open the [calibration management dialog](spectrumcalibration.md)
|
||||
|
||||
<h3>B.5: 3D Spectrogram Controls</h3>
|
||||
|
||||
The 3D Spectrogram controls are only visible when the 3D Spectrogram is being displayed.
|
||||
|
||||
<h4>B.5.1: Style</h4>
|
||||
|
||||
This dropdown determines how the 3D Spectrogram data is rendered.
|
||||
|
||||
- Points: The data are rendeded as points.
|
||||
- Lines: The data points are connected by lines.
|
||||
- Solid: The data are rendeded as a solid surface with constant illumination.
|
||||
- Outline: The data are rendered as a solid surface with outlines of the polygons highlighted.
|
||||
- Shaded: The data are rendeder as a solid surface with a combination of ambient and diffuse lighting.
|
||||
|
||||
<h4>B.5.2: Color Map</h4>
|
||||
|
||||
This dropdown allows the selection of a number of pre-defined color maps that are used for rendering the 3D Spectrogram.
|
||||
'Angel' is the default SDRangel color map.
|
||||
|
||||
<h2>3D Spectrogram Controls</h2>
|
||||
|
||||
The 3D Spectrogram view can be controlled by mouse/trackpad:
|
||||
|
||||
- Left button: Rotate
|
||||
- Middle button: Pan
|
||||
- Right button: Scale Z axis (power)
|
||||
- Wheel/pinch: Zoom
|
||||
|
||||
Or keyboard:
|
||||
|
||||
- Arrow: Rotate
|
||||
- CTRL Arrow: Pan
|
||||
- '+'/'-': Zoom
|
||||
- CTRL '+'/'-': Scale Z axis (power)
|
||||
- 'r': Reset view to default
|
||||
- 'f': Flat view (top down)
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "gui/aboutdialog.h"
|
||||
#include "gui/rollupwidget.h"
|
||||
#include "gui/audiodialog.h"
|
||||
#include "gui/graphicsdialog.h"
|
||||
#include "gui/loggingdialog.h"
|
||||
#include "gui/deviceuserargsdialog.h"
|
||||
#include "gui/sdrangelsplash.h"
|
||||
@ -1455,6 +1456,9 @@ void MainWindow::createMenuBar()
|
||||
QAction *audioAction = preferencesMenu->addAction("&Audio...");
|
||||
audioAction->setToolTip("Audio preferences");
|
||||
QObject::connect(audioAction, &QAction::triggered, this, &MainWindow::on_action_Audio_triggered);
|
||||
QAction *graphicsAction = preferencesMenu->addAction("&Graphics...");
|
||||
graphicsAction->setToolTip("Graphics preferences");
|
||||
QObject::connect(graphicsAction, &QAction::triggered, this, &MainWindow::on_action_Graphics_triggered);
|
||||
QAction *loggingAction = preferencesMenu->addAction("&Logging...");
|
||||
loggingAction->setToolTip("Logging preferences");
|
||||
QObject::connect(loggingAction, &QAction::triggered, this, &MainWindow::on_action_Logging_triggered);
|
||||
@ -2029,6 +2033,12 @@ void MainWindow::on_action_Audio_triggered()
|
||||
audioDialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Graphics_triggered()
|
||||
{
|
||||
GraphicsDialog graphicsDialog(m_mainCore->m_settings, this);
|
||||
graphicsDialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::on_action_Logging_triggered()
|
||||
{
|
||||
LoggingDialog loggingDialog(m_mainCore->m_settings, this);
|
||||
|
@ -179,6 +179,7 @@ private slots:
|
||||
void on_action_saveAll_triggered();
|
||||
void on_action_Configurations_triggered();
|
||||
void on_action_Audio_triggered();
|
||||
void on_action_Graphics_triggered();
|
||||
void on_action_Logging_triggered();
|
||||
void on_action_FFT_triggered();
|
||||
void on_action_My_Position_triggered();
|
||||
|
@ -19,6 +19,7 @@ The menu items from left to right are:
|
||||
- Preferences:
|
||||
- _Configurations_: opens a dialog to manage instance configurations. See configurations dialog documentation [here](configurations.md)
|
||||
- _Audio_: opens a dialog to choose the audio output device. See the audio management documentation [here](audio.md)
|
||||
- _Graphics_: opens a dialog to choose graphics options.
|
||||
- _Logging_: opens a dialog to choose logging options. See "Logging" paragraph next for details
|
||||
- _FFT_: opens a dialog to run the `fftwf-wisdom` utility with a choice of direct and possibly reverse FFT sizes. It produces a so called wisdom file `fftw-wisdom` that speeds up FFT allocations. It is created at a default location and will be used at next invocations of SDRangel. See "FFT" paragraph next for details.
|
||||
- _My Position_: opens a dialog to enter your station ("My Position") coordinates in decimal degrees with north latitudes positive and east longitudes positive. This is used whenever positional data is to be displayed (APRS, DPRS, ...). For it now only works with D-Star $$CRC frames. See [DSD demod plugin](../plugins/channelrx/demoddsd/readme.md) for details on how to decode Digital Voice modes.
|
||||
@ -429,6 +430,20 @@ This will delete the currently selected command or if selection is a group this
|
||||
|
||||
Use this button to activate the keyboard bindings. Note that you need to have this button selected (its background should be lit in beige/orange) for the key bindings to be effective.
|
||||
|
||||
<h3>2.4: Graphics</h3>
|
||||
|
||||
When clicking on the Graphics submenu a dialog opens for setting graphics options.
|
||||
|
||||
![Main Window Graphics](../doc/img/MainWindow_graphics.png)
|
||||
|
||||
<h4>2.4.1</h4>
|
||||
|
||||
Multisampling (MSAA) determines whether multisampling anti-aliasing is used to removed the jagged edges of lines when rendering 2D and 3D spectra.
|
||||
The higher the number of samples chosen, the better quality the anti-aliasing will be, but higher values require more GPU processing and memory.
|
||||
|
||||
Changing this option will only take effect when spectrum windows are recreated (not just hidden then made visible again), so in some cases it
|
||||
may be necessary to restart SDRangel to see the difference.
|
||||
|
||||
<h2>3: Help</h2>
|
||||
|
||||
<h3>3.1: Loaded plugins</h3>
|
||||
|
BIN
sdrgui/resources/3dspectrogram.png
Normal file
BIN
sdrgui/resources/3dspectrogram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -135,6 +135,7 @@
|
||||
<file>channels.png</file>
|
||||
<file>channels_add.png</file>
|
||||
<file>exit_round.png</file>
|
||||
<file>3dspectrogram.png</file>
|
||||
<file>LiberationMono-Regular.ttf</file>
|
||||
<file>LiberationSans-Regular.ttf</file>
|
||||
</qresource>
|
||||
|
Loading…
Reference in New Issue
Block a user