Merge pull request #1291 from srcejon/spectrogram3d

3D Spectrogram
This commit is contained in:
Edouard Griffiths 2022-06-20 14:06:40 +02:00 committed by GitHub
commit 09c49d0dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 8262 additions and 383 deletions

View File

@ -22,6 +22,8 @@
#include <QStyleFactory>
#include <QFontDatabase>
#include <QSysInfo>
#include <QGLFormat>
#include <QSurfaceFormat>
#include "loggerwithfile.h"
#include "mainwindow.h"
@ -134,10 +136,19 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
int main(int argc, char* argv[])
{
#ifdef __APPLE__
// Enable WebGL in QtWebEngine when OpenGL is only version 2.1 (Needed for 3D Map)
// This can be removed when we eventually request a 4.1 OpenGL context
// This needs to be executed before any other Qt code
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--ignore-gpu-blacklist");
// Request OpenGL 3.3 context, needed for glspectrum and 3D Map feature
// Note that Mac only supports CoreProfile, so any deprecated OpenGL 2 features
// will not work. Because of this, we have two versions of the shaders:
// OpenGL 2 versions for compatiblity with older drivers and OpenGL 3.3
// versions for newer drivers
QGLFormat fmt;
fmt.setVersion(3, 3);
fmt.setProfile(QGLFormat::CoreProfile);
QGLFormat::setDefaultFormat(fmt);
QSurfaceFormat sfc;
sfc.setVersion(3, 3);
sfc.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(sfc);
#endif
qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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
{

View File

@ -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

File diff suppressed because it is too large Load Diff

62
sdrbase/util/colormap.h Normal file
View 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

View File

@ -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

View File

@ -156,6 +156,8 @@ void GLScope::newTraces(std::vector<float *> *traces, int traceIndex, std::vecto
void GLScope::initializeGL()
{
QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext();
int majorVersion = 0;
int minorVersion = 0;
if (glCurrentContext)
{
@ -165,6 +167,8 @@ void GLScope::initializeGL()
<< " major: " << (QOpenGLContext::currentContext()->format()).majorVersion()
<< " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion()
<< " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no");
majorVersion = (QOpenGLContext::currentContext()->format()).majorVersion();
minorVersion = (QOpenGLContext::currentContext()->format()).minorVersion();
}
else
{
@ -205,14 +209,14 @@ void GLScope::initializeGL()
glFunctions->initializeOpenGLFunctions();
//glDisable(GL_DEPTH_TEST);
m_glShaderSimple.initializeGL();
m_glShaderColors.initializeGL();
m_glShaderLeft1Scale.initializeGL();
m_glShaderBottom1Scale.initializeGL();
m_glShaderLeft2Scale.initializeGL();
m_glShaderBottom2Scale.initializeGL();
m_glShaderPowerOverlay.initializeGL();
m_glShaderTextOverlay.initializeGL();
m_glShaderSimple.initializeGL(majorVersion, minorVersion);
m_glShaderColors.initializeGL(majorVersion, minorVersion);
m_glShaderLeft1Scale.initializeGL(majorVersion, minorVersion);
m_glShaderBottom1Scale.initializeGL(majorVersion, minorVersion);
m_glShaderLeft2Scale.initializeGL(majorVersion, minorVersion);
m_glShaderBottom2Scale.initializeGL(majorVersion, minorVersion);
m_glShaderPowerOverlay.initializeGL(majorVersion, minorVersion);
m_glShaderTextOverlay.initializeGL(majorVersion, minorVersion);
}
void GLScope::resizeGL(int width, int height)

View File

@ -27,6 +27,9 @@
GLShaderColors::GLShaderColors() :
m_program(nullptr),
m_vao(nullptr),
m_verticesBuf(nullptr),
m_colorBuf(nullptr),
m_matrixLoc(0),
m_alphaLoc(0)
{ }
@ -36,17 +39,32 @@ GLShaderColors::~GLShaderColors()
cleanup();
}
void GLShaderColors::initializeGL()
void GLShaderColors::initializeGL(int majorVersion, int minorVersion)
{
m_program = new QOpenGLShaderProgram;
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple)) {
qDebug() << "GLShaderColors::initializeGL: error in vertex shader: " << m_program->log();
}
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple)) {
qDebug() << "GLShaderColors::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored)) {
qDebug() << "GLShaderColors::initializeGL: error in fragment shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored)) {
qDebug() << "GLShaderColors::initializeGL: error in fragment shader: " << m_program->log();
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
else
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple2)) {
qDebug() << "GLShaderColors::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored2)) {
qDebug() << "GLShaderColors::initializeGL: error in fragment shader: " << m_program->log();
}
}
m_program->bindAttributeLocation("vertex", 0);
m_program->bindAttributeLocation("v_color", 1);
@ -58,6 +76,16 @@ void GLShaderColors::initializeGL()
m_program->bind();
m_matrixLoc = m_program->uniformLocation("uMatrix");
m_alphaLoc = m_program->uniformLocation("uAlpha");
if (m_vao)
{
m_verticesBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_verticesBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_verticesBuf->create();
m_colorBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_colorBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_colorBuf->create();
m_vao->release();
}
m_program->release();
}
@ -95,30 +123,53 @@ void GLShaderColors::draw(unsigned int mode, const QMatrix4x4& transformMatrix,
f->glEnable(GL_BLEND);
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->glEnableVertexAttribArray(1); // colors
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, colors);
if (m_vao)
{
m_vao->bind();
m_verticesBuf->bind();
m_verticesBuf->allocate(vertices, nbVertices * 2 * sizeof(GL_FLOAT));
m_program->enableAttributeArray(0);
m_program->setAttributeBuffer(0, GL_FLOAT, 0, 2);
m_colorBuf->bind();
m_colorBuf->allocate(colors, nbVertices * 3 * sizeof(GL_FLOAT));
m_program->enableAttributeArray(1);
m_program->setAttributeBuffer(1, GL_FLOAT, 0, 3);
}
else
{
f->glEnableVertexAttribArray(0); // vertex
f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
f->glEnableVertexAttribArray(1); // colors
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, colors);
}
f->glDrawArrays(mode, 0, nbVertices);
f->glDisableVertexAttribArray(0);
f->glDisableVertexAttribArray(1);
if (m_vao)
{
m_vao->release();
}
else
{
f->glDisableVertexAttribArray(0);
f->glDisableVertexAttribArray(1);
}
m_program->release();
}
void GLShaderColors::cleanup()
{
if (!QOpenGLContext::currentContext()) {
return;
}
if (m_program)
{
delete m_program;
m_program = nullptr;
}
delete m_program;
m_program = nullptr;
delete m_vao;
m_vao = nullptr;
delete m_verticesBuf;
m_verticesBuf = nullptr;
delete m_colorBuf;
m_colorBuf = nullptr;
}
const QString GLShaderColors::m_vertexShaderSourceSimple = QString(
const QString GLShaderColors::m_vertexShaderSourceSimple2 = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"attribute vec3 v_color;\n"
@ -129,10 +180,32 @@ const QString GLShaderColors::m_vertexShaderSourceSimple = QString(
"}\n"
);
const QString GLShaderColors::m_fragmentShaderSourceColored = QString(
const QString GLShaderColors::m_vertexShaderSourceSimple = QString(
"#version 330\n"
"uniform highp mat4 uMatrix;\n"
"in highp vec4 vertex;\n"
"in vec3 v_color;\n"
"out vec3 f_color;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" f_color = v_color;\n"
"}\n"
);
const QString GLShaderColors::m_fragmentShaderSourceColored2 = QString(
"uniform mediump float uAlpha;\n"
"varying vec3 f_color;\n"
"void main() {\n"
" gl_FragColor = vec4(f_color.r, f_color.g, f_color.b, uAlpha);\n"
"}\n"
);
const QString GLShaderColors::m_fragmentShaderSourceColored = QString(
"#version 330\n"
"uniform mediump float uAlpha;\n"
"in vec3 f_color;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vec4(f_color.r, f_color.g, f_color.b, uAlpha);\n"
"}\n"
);

View File

@ -24,6 +24,8 @@
#include <QString>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include "export.h"
@ -37,7 +39,7 @@ public:
GLShaderColors();
~GLShaderColors();
void initializeGL();
void initializeGL(int majorVersion, int minorVersion);
void drawPoints(const QMatrix4x4& transformMatrix, GLfloat *vertices, GLfloat *colors, GLfloat alpha, int nbVertices);
void drawPolyline(const QMatrix4x4& transformMatrix, GLfloat *vertices, GLfloat *colors, GLfloat alpha, int nbVertices);
void drawSegments(const QMatrix4x4& transformMatrix, GLfloat *vertices, GLfloat *colors, GLfloat alpha, int nbVertices);
@ -49,9 +51,14 @@ private:
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *vertices, GLfloat *colors, GLfloat alpha, int nbVertices);
QOpenGLShaderProgram *m_program;
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_verticesBuf;
QOpenGLBuffer *m_colorBuf;
int m_matrixLoc;
int m_alphaLoc;
static const QString m_vertexShaderSourceSimple2;
static const QString m_vertexShaderSourceSimple;
static const QString m_fragmentShaderSourceColored2;
static const QString m_fragmentShaderSourceColored;
};

View File

@ -26,101 +26,164 @@
#include "gui/glshadersimple.h"
GLShaderSimple::GLShaderSimple() :
m_program(0),
m_program(nullptr),
m_vao(nullptr),
m_verticesBuf(nullptr),
m_vertexLoc(0),
m_matrixLoc(0),
m_colorLoc(0)
m_colorLoc(0)
{ }
GLShaderSimple::~GLShaderSimple()
{
cleanup();
cleanup();
}
void GLShaderSimple::initializeGL()
void GLShaderSimple::initializeGL(int majorVersion, int minorVersion)
{
m_program = new QOpenGLShaderProgram;
m_program = new QOpenGLShaderProgram;
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple)) {
qDebug() << "GLShaderSimple::initializeGL: error in vertex shader: " << m_program->log();
}
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple)) {
qDebug() << "GLShaderSimple::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored)) {
qDebug() << "GLShaderSimple::initializeGL: error in fragment shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored)) {
qDebug() << "GLShaderSimple::initializeGL: error in fragment shader: " << m_program->log();
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
else
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceSimple2)) {
qDebug() << "GLShaderSimple::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceColored2)) {
qDebug() << "GLShaderSimple::initializeGL: error in fragment shader: " << m_program->log();
}
}
m_program->bindAttributeLocation("vertex", 0);
m_program->bindAttributeLocation("vertex", 0);
if (!m_program->link()) {
qDebug() << "GLShaderSimple::initializeGL: error linking shader: " << m_program->log();
}
if (!m_program->link()) {
qDebug() << "GLShaderSimple::initializeGL: error linking shader: " << m_program->log();
}
m_program->bind();
m_matrixLoc = m_program->uniformLocation("uMatrix");
m_colorLoc = m_program->uniformLocation("uColour");
m_program->release();
m_program->bind();
m_vertexLoc = m_program->attributeLocation("vertex");
m_matrixLoc = m_program->uniformLocation("uMatrix");
m_colorLoc = m_program->uniformLocation("uColour");
if (m_vao)
{
m_verticesBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_verticesBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_verticesBuf->create();
m_vao->release();
}
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();
m_program->setUniformValue(m_matrixLoc, transformMatrix);
m_program->setUniformValue(m_colorLoc, color);
f->glEnable(GL_BLEND);
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->glDrawArrays(mode, 0, nbVertices);
f->glDisableVertexAttribArray(0);
m_program->release();
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_program->bind();
m_program->setUniformValue(m_matrixLoc, transformMatrix);
m_program->setUniformValue(m_colorLoc, color);
if (m_vao)
{
m_vao->bind();
m_verticesBuf->bind();
m_verticesBuf->allocate(vertices, nbVertices * nbComponents * sizeof(GL_FLOAT));
m_program->enableAttributeArray(m_vertexLoc);
m_program->setAttributeBuffer(m_vertexLoc, GL_FLOAT, 0, nbComponents);
}
else
{
f->glEnableVertexAttribArray(m_vertexLoc); // vertex
f->glVertexAttribPointer(m_vertexLoc, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
}
f->glEnable(GL_BLEND);
f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
f->glLineWidth(1.0f);
f->glDrawArrays(mode, 0, nbVertices);
if (m_vao) {
m_vao->release();
} else {
f->glDisableVertexAttribArray(m_vertexLoc);
}
m_program->release();
}
void GLShaderSimple::cleanup()
{
if (QOpenGLContext::currentContext() && m_program)
{
delete m_program;
m_program = nullptr;
}
delete m_program;
m_program = nullptr;
delete m_vao;
m_vao = nullptr;
delete m_verticesBuf;
m_verticesBuf = nullptr;
}
const QString GLShaderSimple::m_vertexShaderSourceSimple2 = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
"}\n"
);
const QString GLShaderSimple::m_vertexShaderSourceSimple = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
"}\n"
);
"#version 330\n"
"uniform highp mat4 uMatrix;\n"
"in highp vec4 vertex;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
"}\n"
);
const QString GLShaderSimple::m_fragmentShaderSourceColored2 = QString(
"uniform mediump vec4 uColour;\n"
"void main() {\n"
" gl_FragColor = uColour;\n"
"}\n"
);
const QString GLShaderSimple::m_fragmentShaderSourceColored = QString(
"uniform mediump vec4 uColour;\n"
"void main() {\n"
" gl_FragColor = uColour;\n"
"}\n"
);
"#version 330\n"
"out vec4 fragColor;\n"
"uniform mediump vec4 uColour;\n"
"void main() {\n"
" fragColor = uColour;\n"
"}\n"
);

View File

@ -21,6 +21,8 @@
#include <QString>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include "export.h"
@ -34,21 +36,26 @@ public:
GLShaderSimple();
~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 initializeGL(int majorVersion, int minorVersion);
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;
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_verticesBuf;
int m_vertexLoc;
int m_matrixLoc;
int m_colorLoc;
static const QString m_vertexShaderSourceSimple2;
static const QString m_vertexShaderSourceSimple;
static const QString m_fragmentShaderSourceColored2;
static const QString m_fragmentShaderSourceColored;
};

View File

@ -0,0 +1,846 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QOpenGLFunctions_3_3_Core>
#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_coord2dLoc(0),
m_textureTransformLoc(0),
m_vertexTransformLoc(0),
m_dataTextureLoc(0),
m_limitLoc(0),
m_brightnessLoc(0),
m_colorMapLoc(0),
m_lightDirLoc(0),
m_lightPosLoc(0),
m_useImmutableStorage(true),
m_vao(nullptr),
m_vertexBuf(nullptr),
m_index0Buf(nullptr),
m_index1Buf(nullptr),
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(int majorVersion, int minorVersion)
{
initializeOpenGLFunctions();
m_useImmutableStorage = useImmutableStorage();
qDebug() << "GLShaderSpectrogram::initializeGL: m_useImmutableStorage: " << m_useImmutableStorage;
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
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();
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
else
{
m_programSimple = new QOpenGLShaderProgram;
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShader2)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in vertex shader: " << m_programSimple->log();
}
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSimple2)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in fragment shader: " << m_programSimple->log();
}
if (!m_programSimple->link()) {
qDebug() << "GLShaderSpectrogram::initializeGL: error linking shader: " << m_programSimple->log();
}
}
m_vertexBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vertexBuf->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vertexBuf->create();
m_index0Buf = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
m_index0Buf->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_index0Buf->create();
m_index1Buf = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
m_index1Buf->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_index1Buf->create();
if (m_vao) {
m_vao->release();
}
}
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);
}
}
if (m_vao) {
m_vao->bind();
}
m_vertexBuf->bind();
m_vertexBuf->allocate(&vertices[0], vertices.size() * sizeof(QVector2D));
if (m_vao)
{
m_programShaded->enableAttributeArray(m_coord2dLoc);
m_programShaded->setAttributeBuffer(m_coord2dLoc, GL_FLOAT, 0, 2);
m_programSimple->enableAttributeArray(m_coord2dLoc);
m_programSimple->setAttributeBuffer(m_coord2dLoc, GL_FLOAT, 0, 2);
m_vao->release();
}
std::vector<GLuint> indices(m_gridElements * m_gridElements * 6);
int i;
// Create an array of indices into the vertex array that traces both horizontal and vertical lines
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 an 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));
if (!m_vao)
{
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_RED,
image.width(), image.height(), 0, GL_RED, 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_RED, 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_RED, GL_UNSIGNED_BYTE, pixels);
}
void GLShaderSpectrogram::drawSurface(SpectrumSettings::SpectrogramStyle style, const QMatrix4x4& vertexTransform, 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;
}
if (!program) {
return;
}
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_coord2dLoc = program->attributeLocation("coord2d");
m_textureTransformLoc = program->uniformLocation("textureTransform");
m_vertexTransformLoc = program->uniformLocation("vertexTransform");
m_dataTextureLoc = program->uniformLocation("dataTexture");
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_dataTextureLoc, 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);
if (m_vao)
{
m_vao->bind();
}
else
{
m_vertexBuf->bind();
f->glEnableVertexAttribArray(m_coord2dLoc);
f->glVertexAttribPointer(m_coord2dLoc, 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);
}
if (m_vao)
{
m_vao->release();
m_index0Buf->release();
}
else
{
f->glDisableVertexAttribArray(m_coord2dLoc);
// Need to do this, otherwise nothing else is drawn by other shaders
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()
{
delete m_programShaded;
m_programShaded = nullptr;
delete m_programSimple;
m_programSimple = nullptr;
m_programForLocs = nullptr;
delete m_texture;
m_texture = nullptr;
delete m_colorMapTexture;
m_colorMapTexture = nullptr;
delete m_vertexBuf;
m_vertexBuf = nullptr;
delete m_index0Buf;
m_index0Buf = nullptr;
delete m_index1Buf;
m_index1Buf = nullptr;
if (!QOpenGLContext::currentContext()) {
return;
}
if (m_textureId)
{
glDeleteTextures(1, &m_textureId);
m_textureId = 0;
}
if (m_colorMapTextureId)
{
glDeleteTextures(1, &m_colorMapTextureId);
m_colorMapTextureId = 0;
}
}
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::applyTransform(QMatrix4x4 &matrix)
{
// 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
matrix.translate(0.0f, 0.0f, -1.65f); // Camera position
matrix.translate(m_translateX, m_translateY, m_translateZ); // User camera position adjustment
matrix.rotate(m_rotX, 1.0f, 0.0f, 0.0f); // User rotation
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); // Scaling
matrix.translate(-0.5f, -0.5f, 0.0f); // Centre at origin for correct rotation
applyPerspective(matrix);
}
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_vertexShader2 = QString(
"attribute vec2 coord2d;\n"
"varying vec4 coord;\n"
"varying float lightDistance;\n"
"uniform mat4 textureTransform;\n"
"uniform mat4 vertexTransform;\n"
"uniform sampler2D dataTexture;\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(dataTexture, coord.xy).r);\n"
" gl_Position = vertexTransform * vec4(coord2d, coord.z, 1);\n"
" lightDistance = length(lightPos - gl_Position.xyz);\n"
"}\n"
);
const QString GLShaderSpectrogram::m_vertexShader = QString(
"#version 330\n"
"in vec2 coord2d;\n"
"out vec4 coord;\n"
"out float lightDistance;\n"
"uniform mat4 textureTransform;\n"
"uniform mat4 vertexTransform;\n"
"uniform sampler2D dataTexture;\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 = (texture(dataTexture, 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(
"#version 330\n"
"in vec4 coord2;\n"
"in vec3 normal;\n"
"in float lightDistance2;\n"
"out vec4 fragColor;\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 = texture(colorMap, coord2.z).r;\n"
" color.g = texture(colorMap, coord2.z).g;\n"
" color.b = texture(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"
" fragColor[0] = relection.r;\n"
" fragColor[1] = relection.g;\n"
" fragColor[2] = relection.b;\n"
" fragColor[3] = 1.0;\n"
"}\n"
);
const QString GLShaderSpectrogram::m_fragmentShaderSimple2 = 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"
);
const QString GLShaderSpectrogram::m_fragmentShaderSimple = QString(
"#version 330\n"
"in vec4 coord;\n"
"out vec4 fragColor;\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"
" fragColor[0] = texture(colorMap, coord.z).r * brightness * factor;\n"
" fragColor[1] = texture(colorMap, coord.z).g * brightness * factor;\n"
" fragColor[2] = texture(colorMap, coord.z).b * brightness * factor;\n"
" fragColor[3] = 1.0;\n"
"}\n"
);

View File

@ -0,0 +1,132 @@
///////////////////////////////////////////////////////////////////////////////////
// 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(int majorVersion, int minorVersion);
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, const QMatrix4x4& vertexTransform, 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 applyTransform(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_coord2dLoc;
int m_textureTransformLoc;
int m_vertexTransformLoc;
int m_dataTextureLoc;
int m_limitLoc;
int m_brightnessLoc;
int m_colorMapLoc;
int m_lightDirLoc;
int m_lightPosLoc;
bool m_useImmutableStorage;
static const QString m_vertexShader2;
static const QString m_vertexShader;
static const QString m_geometryShader;
static const QString m_fragmentShaderShaded;
static const QString m_fragmentShaderSimple2;
static const QString m_fragmentShaderSimple;
QOpenGLVertexArrayObject *m_vao;
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_ */

View File

@ -27,46 +27,77 @@
#include "gui/glshadertextured.h"
GLShaderTextured::GLShaderTextured() :
m_program(nullptr),
m_texture(nullptr),
m_program(nullptr),
m_vao(nullptr),
m_verticesBuf(nullptr),
m_textureCoordsBuf(nullptr),
m_texture(nullptr),
m_textureId(0),
m_matrixLoc(0),
m_textureLoc(0),
m_vertexLoc(0),
m_texCoordLoc(0),
m_matrixLoc(0),
m_textureLoc(0),
m_useImmutableStorage(true)
{ }
GLShaderTextured::~GLShaderTextured()
{
cleanup();
cleanup();
}
void GLShaderTextured::initializeGL()
void GLShaderTextured::initializeGL(int majorVersion, int minorVersion)
{
initializeOpenGLFunctions();
m_useImmutableStorage = useImmutableStorage();
qDebug() << "GLShaderTextured::initializeGL: m_useImmutableStorage: " << m_useImmutableStorage;
m_program = new QOpenGLShaderProgram;
m_program = new QOpenGLShaderProgram;
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceTextured)) {
qDebug() << "GLShaderTextured::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceTextured)) {
qDebug() << "GLShaderTextured::initializeGL: error in fragment shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceTextured)) {
qDebug() << "GLShaderTextured::initializeGL: error in vertex shader: " << m_program->log();
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
else
{
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShaderSourceTextured2)) {
qDebug() << "GLShaderTextured::initializeGL: error in vertex shader: " << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceTextured2)) {
qDebug() << "GLShaderTextured::initializeGL: error in fragment shader: " << m_program->log();
}
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSourceTextured)) {
qDebug() << "GLShaderTextured::initializeGL: error in fragment shader: " << m_program->log();
}
m_program->bindAttributeLocation("vertex", 0);
m_program->bindAttributeLocation("texCoord", 1);
m_program->bindAttributeLocation("vertex", 0);
m_program->bindAttributeLocation("texCoord", 1);
if (!m_program->link()) {
qDebug() << "GLShaderTextured::initializeGL: error linking shader: " << m_program->log();
}
if (!m_program->link()) {
qDebug() << "GLShaderTextured::initializeGL: error linking shader: " << m_program->log();
}
m_program->bind();
m_matrixLoc = m_program->uniformLocation("uMatrix");
m_textureLoc = m_program->uniformLocation("uTexture");
m_program->release();
m_program->bind();
m_vertexLoc = m_program->attributeLocation("vertex");
m_texCoordLoc = m_program->attributeLocation("texCoord");
m_matrixLoc = m_program->uniformLocation("uMatrix");
m_textureLoc = m_program->uniformLocation("uTexture");
if (m_vao)
{
m_verticesBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_verticesBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_verticesBuf->create();
m_textureCoordsBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_textureCoordsBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_textureCoordsBuf->create();
m_vao->release();
}
m_program->release();
}
void GLShaderTextured::initTexture(const QImage& image, QOpenGLTexture::WrapMode wrapMode)
@ -80,34 +111,34 @@ void GLShaderTextured::initTexture(const QImage& image, QOpenGLTexture::WrapMode
void GLShaderTextured::initTextureImmutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode)
{
if (m_texture) {
delete m_texture;
}
if (m_texture) {
delete m_texture;
}
m_texture = new QOpenGLTexture(image);
m_texture = new QOpenGLTexture(image);
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(wrapMode);
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(wrapMode);
}
void GLShaderTextured::initTextureMutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode)
{
if (m_textureId)
if (m_textureId)
{
glDeleteTextures(1, &m_textureId);
m_textureId = 0;
}
glDeleteTextures(1, &m_textureId);
m_textureId = 0;
}
glGenTextures(1, &m_textureId);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constScanLine(0));
glGenTextures(1, &m_textureId);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,
image.width(), image.height(), 0, GL_RGBA, 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, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
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, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
}
void GLShaderTextured::subTexture(int xOffset, int yOffset, int width, int height, const void *pixels)
@ -121,98 +152,125 @@ void GLShaderTextured::subTexture(int xOffset, int yOffset, int width, int heigh
void GLShaderTextured::subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels)
{
if (!m_texture)
if (!m_texture)
{
qDebug("GLShaderTextured::subTextureImmutable: no texture defined. Doing nothing");
return;
}
qDebug("GLShaderTextured::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_RGBA, GL_UNSIGNED_BYTE, pixels);
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_texture->bind();
f->glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
void GLShaderTextured::subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels)
{
if (!m_textureId)
if (!m_textureId)
{
qDebug("GLShaderTextured::subTextureMutable: no texture defined. Doing nothing");
return;
}
qDebug("GLShaderTextured::subTextureMutable: no texture defined. Doing nothing");
return;
}
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, m_textureId);
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)
if (!m_texture)
{
qDebug("GLShaderTextured::draw: no texture defined. Doing nothing");
return;
}
qDebug("GLShaderTextured::draw: no texture defined. Doing nothing");
return;
}
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_program->bind();
m_program->setUniformValue(m_matrixLoc, 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->glEnableVertexAttribArray(1); // texture coordinates
f->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
f->glDrawArrays(mode, 0, nbVertices);
f->glDisableVertexAttribArray(0);
m_program->release();
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_program->bind();
m_program->setUniformValue(m_matrixLoc, transformMatrix);
m_texture->bind();
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
if (m_vao)
{
m_vao->bind();
m_verticesBuf->bind();
m_verticesBuf->allocate(vertices, nbVertices * nbComponents * sizeof(GL_FLOAT));
m_program->enableAttributeArray(m_vertexLoc);
m_program->setAttributeBuffer(m_vertexLoc, GL_FLOAT, 0, nbComponents);
m_textureCoordsBuf->bind();
m_textureCoordsBuf->allocate(textureCoords, nbVertices * 2 * sizeof(GL_FLOAT));
m_program->enableAttributeArray(m_texCoordLoc);
m_program->setAttributeBuffer(m_texCoordLoc, GL_FLOAT, 0, 2);
}
else
{
f->glEnableVertexAttribArray(m_vertexLoc); // vertex
f->glVertexAttribPointer(m_vertexLoc, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
f->glEnableVertexAttribArray(m_texCoordLoc); // texture coordinates
f->glVertexAttribPointer(m_texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
}
f->glDrawArrays(mode, 0, nbVertices);
if (m_vao)
{
m_vao->release();
}
else
{
f->glDisableVertexAttribArray(m_vertexLoc);
f->glDisableVertexAttribArray(m_texCoordLoc);
}
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)
if (!m_textureId)
{
qDebug("GLShaderTextured::drawMutable: no texture defined. Doing nothing");
return;
}
qDebug("GLShaderTextured::drawMutable: no texture defined. Doing nothing");
return;
}
m_program->bind();
m_program->setUniformValue(m_matrixLoc, transformMatrix);
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);
glEnableVertexAttribArray(1); // texture coordinates
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
glDrawArrays(mode, 0, nbVertices);
glDisableVertexAttribArray(0);
m_program->release();
m_program->bind();
m_program->setUniformValue(m_matrixLoc, transformMatrix);
glBindTexture(GL_TEXTURE_2D, m_textureId);
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
glEnableVertexAttribArray(m_vertexLoc); // vertex
glVertexAttribPointer(m_vertexLoc, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(m_texCoordLoc); // texture coordinates
glVertexAttribPointer(m_texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
glDrawArrays(mode, 0, nbVertices);
glDisableVertexAttribArray(m_vertexLoc);
glDisableVertexAttribArray(m_texCoordLoc);
m_program->release();
}
void GLShaderTextured::cleanup()
{
if (!QOpenGLContext::currentContext()) {
return;
}
delete m_program;
m_program = nullptr;
delete m_vao;
m_vao = nullptr;
delete m_verticesBuf;
m_verticesBuf = nullptr;
delete m_textureCoordsBuf;
m_textureCoordsBuf = nullptr;
delete m_texture;
m_texture = nullptr;
if (m_program)
{
delete m_program;
m_program = nullptr;
}
if (m_texture)
{
delete m_texture;
m_texture = nullptr;
}
if (!QOpenGLContext::currentContext()) {
return;
}
if (m_textureId)
{
@ -248,21 +306,43 @@ bool GLShaderTextured::useImmutableStorage()
return false;
}
const QString GLShaderTextured::m_vertexShaderSourceTextured2 = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n"
);
const QString GLShaderTextured::m_vertexShaderSourceTextured = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n"
);
"#version 330\n"
"uniform highp mat4 uMatrix;\n"
"in highp vec4 vertex;\n"
"in highp vec2 texCoord;\n"
"out mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n"
);
const QString GLShaderTextured::m_fragmentShaderSourceTextured2 = QString(
"uniform lowp sampler2D uTexture;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_FragColor = texture2D(uTexture, texCoordVar);\n"
"}\n"
);
const QString GLShaderTextured::m_fragmentShaderSourceTextured = QString(
"uniform lowp sampler2D uTexture;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_FragColor = texture2D(uTexture, texCoordVar);\n"
"}\n"
);
"#version 330\n"
"uniform lowp sampler2D uTexture;\n"
"in mediump vec2 texCoordVar;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = texture(uTexture, texCoordVar);\n"
"}\n"
);

View File

@ -25,6 +25,8 @@
#include <QString>
#include <QOpenGLTexture>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include "export.h"
@ -35,32 +37,39 @@ class QImage;
class SDRGUI_API GLShaderTextured : protected QOpenGLFunctions
{
public:
GLShaderTextured();
~GLShaderTextured();
GLShaderTextured();
~GLShaderTextured();
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 cleanup();
void initializeGL(int majorVersion, int minorVersion);
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, 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 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);
void subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels);
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);
void subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels);
bool useImmutableStorage();
QOpenGLShaderProgram *m_program;
QOpenGLTexture *m_texture;
QOpenGLShaderProgram *m_program;
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_verticesBuf;
QOpenGLBuffer *m_textureCoordsBuf;
QOpenGLTexture *m_texture;
unsigned int m_textureId;
int m_matrixLoc;
int m_textureLoc;
int m_vertexLoc;
int m_texCoordLoc;
int m_matrixLoc;
int m_textureLoc;
bool m_useImmutableStorage;
static const QString m_vertexShaderSourceTextured;
static const QString m_fragmentShaderSourceTextured;
static const QString m_vertexShaderSourceTextured2;
static const QString m_vertexShaderSourceTextured;
static const QString m_fragmentShaderSourceTextured2;
static const QString m_fragmentShaderSourceTextured;
};
#endif /* INCLUDE_GUI_GLSHADERTEXTURED_H_ */

View File

@ -20,25 +20,48 @@
#include "gui/glshadertvarray.h"
const QString GLShaderTVArray::m_strVertexShaderSourceArray = QString(
const QString GLShaderTVArray::m_strVertexShaderSourceArray2 = QString(
"uniform highp mat4 uMatrix;\n"
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n");
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n");
const QString GLShaderTVArray::m_strVertexShaderSourceArray = QString(
"#version 330\n"
"uniform highp mat4 uMatrix;\n"
"in highp vec4 vertex;\n"
"in highp vec2 texCoord;\n"
"out mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = uMatrix * vertex;\n"
" texCoordVar = texCoord;\n"
"}\n");
const QString GLShaderTVArray::m_strFragmentShaderSourceColored2 = QString(
"uniform lowp sampler2D uTexture;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_FragColor = texture2D(uTexture, texCoordVar);\n"
"}\n");
const QString GLShaderTVArray::m_strFragmentShaderSourceColored = QString(
"#version 330\n"
"uniform lowp sampler2D uTexture;\n"
"varying mediump vec2 texCoordVar;\n"
"void main() {\n"
" gl_FragColor = texture2D(uTexture, texCoordVar);\n"
"}\n");
"in mediump vec2 texCoordVar;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = texture(uTexture, texCoordVar);\n"
"}\n");
GLShaderTVArray::GLShaderTVArray(bool blnColor) :
m_objProgram(nullptr),
m_vao(nullptr),
m_verticesBuf(nullptr),
m_textureCoordsBuf(nullptr),
m_matrixLoc(0),
m_textureLoc(0),
m_objImage(nullptr),
@ -59,7 +82,7 @@ GLShaderTVArray::~GLShaderTVArray()
cleanup();
}
void GLShaderTVArray::initializeGL(int intCols, int intRows)
void GLShaderTVArray::initializeGL(int majorVersion, int minorVersion, int intCols, int intRows)
{
QMatrix4x4 objQMatrix;
@ -73,20 +96,43 @@ void GLShaderTVArray::initializeGL(int intCols, int intRows)
if (!m_objProgram)
{
m_objProgram = new QOpenGLShaderProgram();
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
m_strVertexShaderSourceArray))
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
qDebug() << "GLShaderArray::initializeGL: error in vertex shader: "
<< m_objProgram->log();
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
m_strVertexShaderSourceArray))
{
qDebug() << "GLShaderArray::initializeGL: error in vertex shader: "
<< m_objProgram->log();
}
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
m_strFragmentShaderSourceColored))
{
qDebug()
<< "GLShaderArray::initializeGL: error in fragment shader: "
<< m_objProgram->log();
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
m_strFragmentShaderSourceColored))
else
{
qDebug()
<< "GLShaderArray::initializeGL: error in fragment shader: "
<< m_objProgram->log();
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Vertex,
m_strVertexShaderSourceArray2))
{
qDebug() << "GLShaderArray::initializeGL: error in vertex shader: "
<< m_objProgram->log();
}
if (!m_objProgram->addShaderFromSourceCode(QOpenGLShader::Fragment,
m_strFragmentShaderSourceColored2))
{
qDebug()
<< "GLShaderArray::initializeGL: error in fragment shader: "
<< m_objProgram->log();
}
}
m_objProgram->bindAttributeLocation("vertex", 0);
@ -100,6 +146,16 @@ void GLShaderTVArray::initializeGL(int intCols, int intRows)
m_objProgram->bind();
m_objProgram->setUniformValue(m_matrixLoc, objQMatrix);
m_objProgram->setUniformValue(m_textureLoc, 0);
if (m_vao)
{
m_verticesBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_verticesBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_verticesBuf->create();
m_textureCoordsBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_textureCoordsBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_textureCoordsBuf->create();
m_vao->release();
}
m_objProgram->release();
}
@ -232,17 +288,41 @@ void GLShaderTVArray::RenderPixels(unsigned char *chrData)
ptrF->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_intCols, m_intRows, GL_RGBA,
GL_UNSIGNED_BYTE, m_objImage->bits());
ptrF->glEnableVertexAttribArray(0); // vertex
ptrF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, arrVertices);
if (m_vao)
{
m_vao->bind();
ptrF->glEnableVertexAttribArray(1); // texture coordinates
ptrF->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords);
m_verticesBuf->bind();
m_verticesBuf->allocate(arrVertices, intNbVertices * 2 * sizeof(GL_FLOAT));
m_objProgram->enableAttributeArray(0);
m_objProgram->setAttributeBuffer(0, GL_FLOAT, 0, 2);
m_textureCoordsBuf->bind();
m_textureCoordsBuf->allocate(arrTextureCoords, intNbVertices * 2 * sizeof(GL_FLOAT));
m_objProgram->enableAttributeArray(1);
m_objProgram->setAttributeBuffer(1, GL_FLOAT, 0, 2);
}
else
{
ptrF->glEnableVertexAttribArray(0); // vertex
ptrF->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, arrVertices);
ptrF->glEnableVertexAttribArray(1); // texture coordinates
ptrF->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords);
}
ptrF->glDrawArrays(GL_TRIANGLES, 0, intNbVertices);
//cleanup
ptrF->glDisableVertexAttribArray(0);
ptrF->glDisableVertexAttribArray(1);
if (m_vao)
{
m_vao->release();
}
else
{
ptrF->glDisableVertexAttribArray(0);
ptrF->glDisableVertexAttribArray(1);
}
//*********************//
@ -294,6 +374,13 @@ void GLShaderTVArray::cleanup()
delete m_objImage;
m_objImage = nullptr;
}
delete m_verticesBuf;
m_verticesBuf = nullptr;
delete m_textureCoordsBuf;
m_textureCoordsBuf = nullptr;
delete m_vao;
m_vao = nullptr;
}
bool GLShaderTVArray::SelectRow(int intLine)

View File

@ -26,6 +26,8 @@
#include <QOpenGLFunctions_3_0>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QMatrix4x4>
#include <QVector4D>
#include <QDebug>
@ -45,7 +47,7 @@ public:
void setColor(bool blnColor) { m_blnColor = blnColor; }
void setAlphaBlend(bool blnAlphaBlend) { m_blnAlphaBlend = blnAlphaBlend; }
void setAlphaReset() { m_blnAlphaReset = true; }
void initializeGL(int intCols, int intRows);
void initializeGL(int majorVersion, int minorVersion, int intCols, int intRows);
void cleanup();
QRgb *GetRowBuffer(int intRow);
void RenderPixels(unsigned char *chrData);
@ -57,10 +59,15 @@ public:
protected:
QOpenGLShaderProgram *m_objProgram;
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_verticesBuf;
QOpenGLBuffer *m_textureCoordsBuf;
int m_matrixLoc;
int m_textureLoc;
//int m_objColorLoc;
static const QString m_strVertexShaderSourceArray2;
static const QString m_strVertexShaderSourceArray;
static const QString m_strFragmentShaderSourceColored2;
static const QString m_strFragmentShaderSourceColored;
QImage *m_objImage;

View File

@ -23,8 +23,11 @@
#include <QOpenGLFunctions>
#include <QPainter>
#include <QFontDatabase>
#include <QWindow>
#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 +82,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 +104,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 +202,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 +218,12 @@ GLSpectrum::~GLSpectrum()
m_waterfallBuffer = nullptr;
}
if (m_3DSpectrogramBuffer)
{
delete m_3DSpectrogramBuffer;
m_3DSpectrogramBuffer = nullptr;
}
if (m_histogramBuffer)
{
delete m_histogramBuffer;
@ -202,6 +235,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 +335,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 +601,7 @@ void GLSpectrum::newSpectrum(const Real *spectrum, int nbBins, int fftSize)
}
updateWaterfall(spectrum);
update3DSpectrogram(spectrum);
updateHistogram(spectrum);
}
@ -563,6 +628,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;
@ -679,18 +767,45 @@ void GLSpectrum::updateHistogram(const Real *spectrum)
void GLSpectrum::initializeGL()
{
QOpenGLContext *glCurrentContext = QOpenGLContext::currentContext();
int majorVersion = 0;
int minorVersion = 0;
if (glCurrentContext)
{
if (QOpenGLContext::currentContext()->isValid()) {
if (QOpenGLContext::currentContext()->isValid())
{
qDebug() << "GLSpectrum::initializeGL: context:"
<< " major: " << (QOpenGLContext::currentContext()->format()).majorVersion()
<< " minor: " << (QOpenGLContext::currentContext()->format()).minorVersion()
<< " ES: " << (QOpenGLContext::currentContext()->isOpenGLES() ? "yes" : "no");
majorVersion = (QOpenGLContext::currentContext()->format()).majorVersion();
minorVersion = (QOpenGLContext::currentContext()->format()).minorVersion();
}
else {
qDebug() << "GLSpectrum::initializeGL: current context is invalid";
}
// Enable OpenGL debugging
// Disable for release, as some OpenGL drivers are quite verbose and output
// info on every frame
if (false)
{
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
{
@ -704,13 +819,21 @@ void GLSpectrum::initializeGL()
glFunctions->initializeOpenGLFunctions();
//glDisable(GL_DEPTH_TEST);
m_glShaderSimple.initializeGL();
m_glShaderLeftScale.initializeGL();
m_glShaderFrequencyScale.initializeGL();
m_glShaderWaterfall.initializeGL();
m_glShaderHistogram.initializeGL();
m_glShaderTextOverlay.initializeGL();
m_glShaderInfo.initializeGL();
m_glShaderSimple.initializeGL(majorVersion, minorVersion);
m_glShaderLeftScale.initializeGL(majorVersion, minorVersion);
m_glShaderFrequencyScale.initializeGL(majorVersion, minorVersion);
m_glShaderWaterfall.initializeGL(majorVersion, minorVersion);
m_glShaderHistogram.initializeGL(majorVersion, minorVersion);
m_glShaderTextOverlay.initializeGL(majorVersion, minorVersion);
m_glShaderInfo.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogram.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogramTimeScale.initializeGL(majorVersion, minorVersion);
m_glShaderSpectrogramPowerScale.initializeGL(majorVersion, minorVersion);
}
void GLSpectrum::openGLDebug(const QOpenGLDebugMessage &debugMessage)
{
qDebug() << "GLSpectrum::openGLDebug: " << debugMessage;
}
void GLSpectrum::resizeGL(int width, int height)
@ -753,11 +876,46 @@ 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;
int devicePixelRatio;
if (m_display3DSpectrogram)
{
m_glShaderSpectrogram.applyTransform(spectrogramGridMatrix);
// 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
if (window()->windowHandle()) {
devicePixelRatio = window()->windowHandle()->devicePixelRatio();
} else {
devicePixelRatio = 1;
}
glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
m_glShaderSpectrogram.drawSurface(m_3DSpectrogramStyle, spectrogramGridMatrix, prop_y, m_invertedWaterfall);
glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
}
else if (m_displayWaterfall)
{
// paint 2D waterfall
{
GLfloat vtx1[] = {
0, m_invertedWaterfall ? 0.0f : 1.0f,
@ -960,7 +1118,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 +1173,74 @@ void GLSpectrum::paintGL()
}
}
// paint 3D spectrogram scales
if (m_display3DSpectrogram && m_displayGrid)
{
glFunctions->glViewport(0, m_3DSpectrogramBottom*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
{
GLfloat l = m_spectrogramTimePixmap.width() / (GLfloat) width();
GLfloat r = m_rightMargin / (GLfloat) width();
GLfloat h = m_frequencyPixmap.height() / (GLfloat) m_waterfallHeight;
GLfloat vtx1[] = {
-l, -h,
1.0f+r, -h,
1.0f+r, 0.0f,
-l, 0.0f
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderFrequencyScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
GLfloat w = m_spectrogramTimePixmap.width() / (GLfloat) width();
GLfloat h = (m_bottomMargin/2) / (GLfloat) m_waterfallHeight; // m_bottomMargin is fm.ascent
GLfloat vtx1[] = {
-w, 0.0f-h,
0.0f, 0.0f-h,
0.0f, 1.0f+h,
-w, 1.0f+h
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramTimeScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
GLfloat w = m_spectrogramPowerPixmap.width() / (GLfloat) width();
GLfloat h = m_topMargin / (GLfloat) m_spectrogramPowerPixmap.height();
GLfloat vtx1[] = {
-w, 1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f+h,
-w, 1.0f, 1.0f+h,
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramPowerScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4, 3);
}
glFunctions->glViewport(0, 0, width()*devicePixelRatio, height()*devicePixelRatio);
}
// paint max hold lines on top of histogram
if (m_displayMaxHold)
{
@ -1154,6 +1380,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*devicePixelRatio, width()*devicePixelRatio, m_waterfallHeight*devicePixelRatio);
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()*devicePixelRatio, height()*devicePixelRatio);
}
// paint histogram grid
if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid))
{
@ -1566,7 +1914,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 +1959,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 +2021,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 +2058,9 @@ void GLSpectrum::applyChanges()
}
m_leftMargin = m_timeScale.getScaleWidth();
setPowerScale((height() - m_topMargin - m_bottomMargin) / 2.0);
m_leftMargin += 2 * M;
setFrequencyScale();
@ -1772,10 +2108,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 +2166,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 +2215,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 +2380,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 +2412,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 +2481,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 +2583,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 +2624,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 +2869,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 +3012,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 +3134,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 +3180,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 +3257,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 +3274,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 +3459,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 +3539,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 +3626,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 +3788,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);
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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);

View File

@ -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">

View 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();
}

View 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_ */

View 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>

View File

@ -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. This requires OpenGL 3.3 or greater.
<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)

View File

@ -178,7 +178,13 @@ void TVScreen::paintGL()
if ((m_askedCols != 0) && (m_askedRows != 0))
{
m_glShaderArray.initializeGL(m_askedCols, m_askedRows);
int major = 0, minor = 0;
if (QOpenGLContext::currentContext())
{
major = QOpenGLContext::currentContext()->format().majorVersion();
minor = QOpenGLContext::currentContext()->format().minorVersion();
}
m_glShaderArray.initializeGL(major, minor, m_askedCols, m_askedRows);
m_askedCols = 0;
m_askedRows = 0;
}

View File

@ -21,52 +21,101 @@
#include "tvscreenanalog.h"
static const char* vertexShaderSource2 =
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying highp vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = vertex;\n"
" texCoordVar = texCoord;\n"
"}\n";
static const char* vertexShaderSource =
"attribute highp vec4 vertex;\n"
"attribute highp vec2 texCoord;\n"
"varying highp vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = vertex;\n"
" texCoordVar = texCoord;\n"
"}\n";
"#version 330\n"
"in highp vec4 vertex;\n"
"in highp vec2 texCoord;\n"
"out highp vec2 texCoordVar;\n"
"void main() {\n"
" gl_Position = vertex;\n"
" texCoordVar = texCoord;\n"
"}\n";
static const char* fragmentShaderSource2 =
"uniform highp sampler2D tex1;\n"
"uniform highp sampler2D tex2;\n"
"uniform highp float imw;\n"
"uniform highp float imh;\n"
"uniform highp float tlw;\n"
"uniform highp float tlh;\n"
"varying highp vec2 texCoordVar;\n"
"void main() {\n"
" float tlhw = 0.5 * tlw;"
" float tlhh = 0.5 * tlh;"
" float tys = (texCoordVar.y + tlhh) * imh;\n"
" float p1y = floor(tys) * tlh - tlhh;\n"
" float p3y = p1y + tlh;\n"
" float tshift1 = texture2D(tex2, vec2(0.0, p1y)).r;\n"
" float tshift3 = texture2D(tex2, vec2(0.0, p3y)).r;\n"
" float shift1 = (1.0 - tshift1 * 2.0) * tlw;\n"
" float shift3 = (1.0 - tshift3 * 2.0) * tlw;\n"
" float txs1 = (texCoordVar.x + shift1 + tlhw) * imw;\n"
" float txs3 = (texCoordVar.x + shift3 + tlhw) * imw;\n"
" float p1x = floor(txs1) * tlw - tlhw;\n"
" float p3x = floor(txs3) * tlw - tlhw;\n"
" float p2x = p1x + tlw;\n"
" float p4x = p3x + tlw;\n"
" float p1 = texture2D(tex1, vec2(p1x, p1y)).r;\n"
" float p2 = texture2D(tex1, vec2(p2x, p1y)).r;\n"
" float p3 = texture2D(tex1, vec2(p3x, p3y)).r;\n"
" float p4 = texture2D(tex1, vec2(p4x, p3y)).r;\n"
" float p12 = mix(p1, p2, fract(txs1));\n"
" float p34 = mix(p3, p4, fract(txs3));\n"
" float p = mix(p12, p34, fract(tys));\n"
" gl_FragColor = vec4(p);\n"
"}\n";
static const char* fragmentShaderSource =
"uniform highp sampler2D tex1;\n"
"uniform highp sampler2D tex2;\n"
"uniform highp float imw;\n"
"uniform highp float imh;\n"
"uniform highp float tlw;\n"
"uniform highp float tlh;\n"
"varying highp vec2 texCoordVar;\n"
"void main() {\n"
" float tlhw = 0.5 * tlw;"
" float tlhh = 0.5 * tlh;"
" float tys = (texCoordVar.y + tlhh) * imh;\n"
" float p1y = floor(tys) * tlh - tlhh;\n"
" float p3y = p1y + tlh;\n"
" float tshift1 = texture2D(tex2, vec2(0.0, p1y)).r;\n"
" float tshift3 = texture2D(tex2, vec2(0.0, p3y)).r;\n"
" float shift1 = (1.0 - tshift1 * 2.0) * tlw;\n"
" float shift3 = (1.0 - tshift3 * 2.0) * tlw;\n"
" float txs1 = (texCoordVar.x + shift1 + tlhw) * imw;\n"
" float txs3 = (texCoordVar.x + shift3 + tlhw) * imw;\n"
" float p1x = floor(txs1) * tlw - tlhw;\n"
" float p3x = floor(txs3) * tlw - tlhw;\n"
" float p2x = p1x + tlw;\n"
" float p4x = p3x + tlw;\n"
" float p1 = texture2D(tex1, vec2(p1x, p1y)).r;\n"
" float p2 = texture2D(tex1, vec2(p2x, p1y)).r;\n"
" float p3 = texture2D(tex1, vec2(p3x, p3y)).r;\n"
" float p4 = texture2D(tex1, vec2(p4x, p3y)).r;\n"
" float p12 = mix(p1, p2, fract(txs1));\n"
" float p34 = mix(p3, p4, fract(txs3));\n"
" float p = mix(p12, p34, fract(tys));\n"
" gl_FragColor = vec4(p);\n"
"}\n";
"#version 330\n"
"uniform highp sampler2D tex1;\n"
"uniform highp sampler2D tex2;\n"
"uniform highp float imw;\n"
"uniform highp float imh;\n"
"uniform highp float tlw;\n"
"uniform highp float tlh;\n"
"in highp vec2 texCoordVar;\n"
"out vec4 fragColor;\n"
"void main() {\n"
" float tlhw = 0.5 * tlw;"
" float tlhh = 0.5 * tlh;"
" float tys = (texCoordVar.y + tlhh) * imh;\n"
" float p1y = floor(tys) * tlh - tlhh;\n"
" float p3y = p1y + tlh;\n"
" float tshift1 = texture2D(tex2, vec2(0.0, p1y)).r;\n"
" float tshift3 = texture2D(tex2, vec2(0.0, p3y)).r;\n"
" float shift1 = (1.0 - tshift1 * 2.0) * tlw;\n"
" float shift3 = (1.0 - tshift3 * 2.0) * tlw;\n"
" float txs1 = (texCoordVar.x + shift1 + tlhw) * imw;\n"
" float txs3 = (texCoordVar.x + shift3 + tlhw) * imw;\n"
" float p1x = floor(txs1) * tlw - tlhw;\n"
" float p3x = floor(txs3) * tlw - tlhw;\n"
" float p2x = p1x + tlw;\n"
" float p4x = p3x + tlw;\n"
" float p1 = texture(tex1, vec2(p1x, p1y)).r;\n"
" float p2 = texture(tex1, vec2(p2x, p1y)).r;\n"
" float p3 = texture(tex1, vec2(p3x, p3y)).r;\n"
" float p4 = texture(tex1, vec2(p4x, p3y)).r;\n"
" float p12 = mix(p1, p2, fract(txs1));\n"
" float p34 = mix(p3, p4, fract(txs3));\n"
" float p = mix(p12, p34, fract(tys));\n"
" fragColor = vec4(p);\n"
"}\n";
TVScreenAnalog::TVScreenAnalog(QWidget *parent) :
QOpenGLWidget(parent),
m_shader(nullptr),
m_vao(nullptr),
m_verticesBuf(nullptr),
m_textureCoordsBuf(nullptr),
m_imageTexture(nullptr),
m_lineShiftsTexture(nullptr)
{
@ -107,7 +156,14 @@ void TVScreenAnalog::cleanup()
delete m_lineShiftsTexture;
m_lineShiftsTexture = nullptr;
}
}
delete m_verticesBuf;
m_verticesBuf = nullptr;
delete m_textureCoordsBuf;
m_textureCoordsBuf = nullptr;
delete m_vao;
m_vao = nullptr;
}
TVScreenAnalogBuffer *TVScreenAnalog::getBackBuffer()
{
@ -147,21 +203,52 @@ void TVScreenAnalog::initializeGL()
m_shader = new QOpenGLShaderProgram(this);
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in vertex shader:"
<< m_shader->log();
return;
}
int majorVersion = 0, minorVersion = 0;
if (QOpenGLContext::currentContext())
{
majorVersion = QOpenGLContext::currentContext()->format().majorVersion();
minorVersion = QOpenGLContext::currentContext()->format().minorVersion();
}
if ((majorVersion > 3) || ((majorVersion == 3) && (minorVersion >= 3)))
{
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in vertex shader:"
<< m_shader->log();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in fragment shader:"
<< m_shader->log();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in fragment shader:"
<< m_shader->log();
return;
}
m_vao = new QOpenGLVertexArrayObject();
m_vao->create();
m_vao->bind();
}
else
{
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource2))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in vertex shader:"
<< m_shader->log();
return;
}
if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource2))
{
qWarning()
<< "TVScreenAnalog::initializeGL: error in fragment shader:"
<< m_shader->log();
return;
}
}
if (!m_shader->link())
{
@ -179,6 +266,16 @@ void TVScreenAnalog::initializeGL()
m_imageHeightLoc = m_shader->uniformLocation("imh");
m_texelWidthLoc = m_shader->uniformLocation("tlw");
m_texelHeightLoc = m_shader->uniformLocation("tlh");
if (m_vao)
{
m_verticesBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_verticesBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_verticesBuf->create();
m_textureCoordsBuf = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_textureCoordsBuf->setUsagePattern(QOpenGLBuffer::DynamicDraw);
m_textureCoordsBuf->create();
m_vao->release();
}
}
void TVScreenAnalog::initializeTextures(TVScreenAnalogBuffer *buffer)
@ -280,13 +377,40 @@ void TVScreenAnalog::paintGL()
1.0f, 1.0f
};
glVertexAttribPointer(m_vertexAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(m_vertexAttribIndex);
glVertexAttribPointer(m_texCoordAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords);
glEnableVertexAttribArray(m_texCoordAttribIndex);
if (m_vao)
{
m_vao->bind();
m_verticesBuf->bind();
m_verticesBuf->allocate(vertices, 4 * 2 * sizeof(GL_FLOAT));
m_shader->enableAttributeArray(m_vertexAttribIndex);
m_shader->setAttributeBuffer(m_vertexAttribIndex, GL_FLOAT, 0, 2);
// As these coords are constant, this could be moved into the init method
m_textureCoordsBuf->bind();
m_textureCoordsBuf->allocate(arrTextureCoords, 4 * 2 * sizeof(GL_FLOAT));
m_shader->enableAttributeArray(m_texCoordAttribIndex);
m_shader->setAttributeBuffer(m_texCoordAttribIndex, GL_FLOAT, 0, 2);
}
else
{
glVertexAttribPointer(m_vertexAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(m_vertexAttribIndex);
glVertexAttribPointer(m_texCoordAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, arrTextureCoords);
glEnableVertexAttribArray(m_texCoordAttribIndex);
}
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisableVertexAttribArray(m_vertexAttribIndex);
glDisableVertexAttribArray(m_texCoordAttribIndex);
if (m_vao)
{
m_vao->release();
}
else
{
glDisableVertexAttribArray(m_vertexAttribIndex);
glDisableVertexAttribArray(m_texCoordAttribIndex);
}
m_shader->release();
}

View File

@ -31,6 +31,8 @@
#include <QOpenGLTexture>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
class TVScreenAnalogBuffer
{
@ -130,6 +132,9 @@ class SDRGUI_API TVScreenAnalog : public QOpenGLWidget, protected QOpenGLFunctio
TVScreenAnalogBuffer *m_backBuffer;
QOpenGLShaderProgram *m_shader;
QOpenGLVertexArrayObject *m_vao;
QOpenGLBuffer *m_verticesBuf;
QOpenGLBuffer *m_textureCoordsBuf;
QOpenGLTexture *m_imageTexture;
QOpenGLTexture *m_lineShiftsTexture;

View File

@ -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);

View File

@ -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();

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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>