1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 01:18:38 -05:00

Add 3D spectrogram

This commit is contained in:
Jon Beniston 2022-06-18 12:35:44 +01:00
parent 7d1e8f8028
commit d441e6d475
30 changed files with 7364 additions and 70 deletions

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

@ -60,32 +60,32 @@ void GLShaderSimple::initializeGL()
m_program->release();
}
void GLShaderSimple::drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
draw(GL_POINTS, transformMatrix, color, vertices, nbVertices);
draw(GL_POINTS, transformMatrix, color, vertices, nbVertices, nbComponents);
}
void GLShaderSimple::drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
draw(GL_LINE_STRIP, transformMatrix, color, vertices, nbVertices);
draw(GL_LINE_STRIP, transformMatrix, color, vertices, nbVertices, nbComponents);
}
void GLShaderSimple::drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
draw(GL_LINES, transformMatrix, color, vertices, nbVertices);
draw(GL_LINES, transformMatrix, color, vertices, nbVertices, nbComponents);
}
void GLShaderSimple::drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
draw(GL_LINE_LOOP, transformMatrix, color, vertices, nbVertices);
draw(GL_LINE_LOOP, transformMatrix, color, vertices, nbVertices, nbComponents);
}
void GLShaderSimple::drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
draw(GL_TRIANGLE_FAN, transformMatrix, color, vertices, nbVertices);
draw(GL_TRIANGLE_FAN, transformMatrix, color, vertices, nbVertices, nbComponents);
}
void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices)
void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents)
{
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_program->bind();
@ -95,7 +95,7 @@ void GLShaderSimple::draw(unsigned int mode, const QMatrix4x4& transformMatrix,
f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
f->glLineWidth(1.0f);
f->glEnableVertexAttribArray(0); // vertex
f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
f->glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
f->glDrawArrays(mode, 0, nbVertices);
f->glDisableVertexAttribArray(0);
m_program->release();

View File

@ -35,15 +35,15 @@ public:
~GLShaderSimple();
void initializeGL();
void drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void drawPoints(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
void drawPolyline(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
void drawSegments(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
void drawContour(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
void drawSurface(const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents=2);
void cleanup();
private:
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices);
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, const QVector4D& color, GLfloat *vertices, int nbVertices, int nbComponents);
QOpenGLShaderProgram *m_program;
int m_matrixLoc;

View File

@ -0,0 +1,758 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB. //
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QOpenGLContext>
#include <QtOpenGL>
#include <QImage>
#include <QMatrix4x4>
#include <QVector4D>
#include <QDebug>
#include "gui/glshaderspectrogram.h"
#include "util/colormap.h"
GLShaderSpectrogram::GLShaderSpectrogram() :
m_programShaded(nullptr),
m_programSimple(nullptr),
m_texture(nullptr),
m_textureId(0),
m_colorMapTexture(nullptr),
m_colorMapTextureId(0),
m_programForLocs(nullptr),
m_textureTransformLoc(0),
m_vertexTransformLoc(0),
m_textureLoc(0),
m_limitLoc(0),
m_brightnessLoc(0),
m_colorMapLoc(0),
m_lightDirLoc(0),
m_lightPosLoc(0),
m_useImmutableStorage(true),
m_vertexBuf(QOpenGLBuffer::VertexBuffer),
m_index0Buf(QOpenGLBuffer::IndexBuffer),
m_index1Buf(QOpenGLBuffer::IndexBuffer),
m_translateX(0.0),
m_translateY(0.0),
m_translateZ(0.0),
m_rotX(-45.0),
m_rotY(0.0),
m_rotZ(0.0),
m_scaleX(1.0),
m_scaleY(1.0),
m_scaleZ(1.0),
m_userScaleZ(1.0),
m_verticalAngle(45.0f),
m_aspectRatio(1600.0/1200.0),
m_lightTranslateX(0.0),
m_lightTranslateY(0.0),
m_lightTranslateZ(0.0),
m_lightRotX(0.0),
m_lightRotY(0.0),
m_lightRotZ(0.0),
m_gridElements(1024)
{
}
GLShaderSpectrogram::~GLShaderSpectrogram()
{
cleanup();
}
void GLShaderSpectrogram::initializeGL()
{
initializeOpenGLFunctions();
m_useImmutableStorage = useImmutableStorage();
qDebug() << "GLShaderSpectrogram::initializeGL: m_useImmutableStorage: " << m_useImmutableStorage;
m_programShaded = new QOpenGLShaderProgram;
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShader)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in vertex shader: " << m_programShaded->log();
}
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Geometry, m_geometryShader)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in geometry shader: " << m_programShaded->log();
}
if (!m_programShaded->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderShaded)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in fragment shader: " << m_programShaded->log();
}
if (!m_programShaded->link()) {
qDebug() << "GLShaderSpectrogram::initializeGL: error linking shader: " << m_programShaded->log();
}
m_programSimple = new QOpenGLShaderProgram;
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Vertex, m_vertexShader)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in vertex shader: " << m_programSimple->log();
}
if (!m_programSimple->addShaderFromSourceCode(QOpenGLShader::Fragment, m_fragmentShaderSimple)) {
qDebug() << "GLShaderSpectrogram::initializeGL: error in fragment shader: " << m_programSimple->log();
}
if (!m_programSimple->link()) {
qDebug() << "GLShaderSpectrogram::initializeGL: error linking shader: " << m_programSimple->log();
}
}
void GLShaderSpectrogram::initGrid(int elements)
{
m_gridElements = std::min(elements, 4096); // Limit to keep memory requirements realistic
qDebug() << "GLShaderSpectrogram::initGrid: requested: " << elements << " actual: " << m_gridElements;
int e1 = m_gridElements+1;
// Grid vertices
std::vector<QVector2D> vertices(e1 * e1);
for (int i = 0; i < e1; i++)
{
for (int j = 0; j < e1; j++)
{
vertices[i*e1+j].setX(j / (float)m_gridElements);
vertices[i*e1+j].setY(i / (float)m_gridElements);
}
}
m_vertexBuf.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vertexBuf.create();
m_index0Buf.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_index0Buf.create();
m_index1Buf.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_index1Buf.create();
m_vertexBuf.bind();
m_vertexBuf.allocate(&vertices[0], vertices.size() * sizeof(QVector2D));
// Create an array of indices into the vertex array that traces both horizontal and vertical lines
std::vector<GLuint> indices(m_gridElements * m_gridElements * 6);
int i = 0;
for (int y = 0; y < e1; y++) {
for (int x = 0; x < m_gridElements; x++) {
indices[i++] = y * (e1) + x;
indices[i++] = y * (e1) + x + 1;
}
}
for (int x = 0; x < e1; x++) {
for (int y = 0; y < m_gridElements; y++) {
indices[i++] = y * (e1) + x;
indices[i++] = (y + 1) * (e1) + x;
}
}
m_index0Buf.bind();
m_index0Buf.allocate(&indices[0], m_gridElements * (e1) * 4 * sizeof(GLuint));
// Create another array of indices that describes all the triangles needed to create a completely filled surface
i = 0;
for (int y = 0; y < m_gridElements; y++) {
for (int x = 0; x < m_gridElements; x++) {
indices[i++] = y * (e1) + x;
indices[i++] = y * (e1) + x + 1;
indices[i++] = (y + 1) * (e1) + x + 1;
indices[i++] = y * (e1) + x;
indices[i++] = (y + 1) * (e1) + x + 1;
indices[i++] = (y + 1) * (e1) + x;
}
}
m_index1Buf.bind();
m_index1Buf.allocate(&indices[0], indices.size() * sizeof(GLuint));
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glBindBuffer(GL_ARRAY_BUFFER, 0);
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void GLShaderSpectrogram::initColorMapTexture(const QString &colorMapName)
{
if (m_useImmutableStorage) {
initColorMapTextureImmutable(colorMapName);
} else {
initColorMapTextureMutable(colorMapName);
}
}
void GLShaderSpectrogram::initColorMapTextureImmutable(const QString &colorMapName)
{
if (!m_colorMapTexture)
{
m_colorMapTexture = new QOpenGLTexture(QOpenGLTexture::Target1D);
m_colorMapTexture->setFormat(QOpenGLTexture::RGB32F);
m_colorMapTexture->setSize(256);
m_colorMapTexture->allocateStorage();
m_colorMapTexture->setMinificationFilter(QOpenGLTexture::Linear);
m_colorMapTexture->setMagnificationFilter(QOpenGLTexture::Linear);
m_colorMapTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
}
GLfloat *colorMap = (GLfloat *)ColorMap::getColorMap(colorMapName);
if (colorMap) {
m_colorMapTexture->setData(QOpenGLTexture::RGB, QOpenGLTexture::Float32, colorMap);
} else {
qDebug() << "GLShaderSpectrogram::initColorMapTextureImmutable: colorMap " << colorMapName << " not supported";
}
}
void GLShaderSpectrogram::initColorMapTextureMutable(const QString &colorMapName)
{
if (m_colorMapTextureId)
{
glDeleteTextures(1, &m_colorMapTextureId);
m_colorMapTextureId = 0;
}
glGenTextures(1, &m_colorMapTextureId);
glBindTexture(GL_TEXTURE_1D, m_colorMapTextureId);
GLfloat *colorMap = (GLfloat *)ColorMap::getColorMap(colorMapName);
if (colorMap) {
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 256, 0, GL_RGB, GL_FLOAT, colorMap);
} else {
qDebug() << "GLShaderSpectrogram::initColorMapTextureMutable: colorMap " << colorMapName << " not supported";
}
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, QOpenGLTexture::Repeat);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, QOpenGLTexture::Repeat);
}
void GLShaderSpectrogram::initTexture(const QImage& image)
{
if (m_useImmutableStorage) {
initTextureImmutable(image);
} else {
initTextureMutable(image);
}
initGrid(image.width());
}
void GLShaderSpectrogram::initTextureImmutable(const QImage& image)
{
if (m_texture) {
delete m_texture;
}
m_texture = new QOpenGLTexture(image);
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
m_texture->setWrapMode(QOpenGLTexture::Repeat);
}
void GLShaderSpectrogram::initTextureMutable(const QImage& image)
{
if (m_textureId)
{
glDeleteTextures(1, &m_textureId);
m_textureId = 0;
}
glGenTextures(1, &m_textureId);
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,
image.width(), image.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image.constScanLine(0));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, QOpenGLTexture::Repeat);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, QOpenGLTexture::Repeat);
}
void GLShaderSpectrogram::subTexture(int xOffset, int yOffset, int width, int height, const void *pixels)
{
if (m_useImmutableStorage) {
subTextureImmutable(xOffset, yOffset, width, height, pixels);
} else {
subTextureMutable(xOffset, yOffset, width, height, pixels);
}
}
void GLShaderSpectrogram::subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels)
{
if (!m_texture)
{
qDebug("GLShaderSpectrogram::subTextureImmutable: no texture defined. Doing nothing");
return;
}
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
m_texture->bind();
f->glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
}
void GLShaderSpectrogram::subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels)
{
if (!m_textureId)
{
qDebug("GLShaderSpectrogram::subTextureMutable: no texture defined. Doing nothing");
return;
}
glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
}
void GLShaderSpectrogram::drawSurface(SpectrumSettings::SpectrogramStyle style, float textureOffset, bool invert)
{
if ((m_useImmutableStorage && !m_texture) || (!m_useImmutableStorage && !m_textureId))
{
qDebug("GLShaderSpectrogram::drawSurface: no texture defined. Doing nothing");
return;
}
QOpenGLShaderProgram *program;
if (style == SpectrumSettings::Shaded) {
program = m_programShaded;
} else {
program = m_programSimple;
}
// Note that translation to the origin and rotation
// needs to be performed in reverse order to what you
// might normally expect
// See: https://bugreports.qt.io/browse/QTBUG-20752
QMatrix4x4 vertexTransform;
vertexTransform.translate(0.0f, 0.0f, -1.65f);
applyScaleRotate(vertexTransform);
vertexTransform.translate(-0.5f, -0.5f, 0.0f);
applyPerspective(vertexTransform);
float rot = invert ? 1.0 : -1.0;
QMatrix4x4 textureTransform(
1.0, 0.0, 0.0, 0.0,
0.0, rot, 0.0, textureOffset,
0.0, 0.0, rot, 0.0,
0.0, 0.0, 0.0, 1.0); // Use this to move texture for each row of data
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
program->bind();
if (program != m_programForLocs)
{
m_textureTransformLoc = program->uniformLocation("textureTransform");
m_vertexTransformLoc = program->uniformLocation("vertexTransform");
m_textureLoc = program->uniformLocation("texture");
m_limitLoc = program->uniformLocation("limit");
m_brightnessLoc = program->uniformLocation("brightness");
m_colorMapLoc = program->uniformLocation("colorMap");
m_lightDirLoc = program->uniformLocation("lightDir");
m_lightPosLoc = program->uniformLocation("lightPos");
m_programForLocs = program;
}
program->setUniformValue(m_vertexTransformLoc, vertexTransform);
program->setUniformValue(m_textureTransformLoc, textureTransform);
if (m_useImmutableStorage)
{
m_texture->bind();
glActiveTexture(GL_TEXTURE1);
m_colorMapTexture->bind();
glActiveTexture(GL_TEXTURE0);
}
else
{
glBindTexture(GL_TEXTURE_2D, m_textureId);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_1D, m_colorMapTextureId);
glActiveTexture(GL_TEXTURE0);
}
program->setUniformValue(m_textureLoc, 0); // set uniform to texture unit?
program->setUniformValue(m_colorMapLoc, 1);
program->setUniformValue(m_limitLoc, 1.5f*1.0f/(float)(m_gridElements));
if (style == SpectrumSettings::Outline)
{
// When drawing the outline, slightly darken the triangles
// so grid is a bit more visible
program->setUniformValue(m_brightnessLoc, 0.85f);
}
else
{
program->setUniformValue(m_brightnessLoc, 1.0f);
}
if (style == SpectrumSettings::Shaded)
{
QMatrix4x4 matrix;
matrix.rotate(m_lightRotX, 1.0f, 0.0f, 0.0f);
matrix.rotate(m_lightRotY, 0.0f, 1.0f, 0.0f);
matrix.rotate(m_lightRotZ, 0.0f, 0.0f, 1.0f);
QVector3D vector = matrix * QVector3D(0.0f, 0.0f, -1.0f);
GLfloat lightDir[3] = {vector.x(), vector.y(), vector.z()};
GLfloat lightPos[3] = {m_lightTranslateX, m_lightTranslateY, m_lightTranslateZ};
program->setUniformValueArray(m_lightDirLoc, lightDir, 1, 3);
program->setUniformValueArray(m_lightPosLoc, lightPos, 1, 3);
}
f->glEnable(GL_DEPTH_TEST);
f->glPolygonOffset(1, 0);
f->glEnable(GL_POLYGON_OFFSET_FILL);
int attribute_coord2d = program->attributeLocation("coord2d");
f->glEnableVertexAttribArray(attribute_coord2d);
m_vertexBuf.bind();
f->glVertexAttribPointer(attribute_coord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
m_index1Buf.bind();
switch (style)
{
case SpectrumSettings::Points:
f->glDrawElements(GL_POINTS, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
break;
case SpectrumSettings::Lines:
f->glDrawElements(GL_LINES, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
break;
case SpectrumSettings::Solid:
case SpectrumSettings::Outline:
case SpectrumSettings::Shaded:
f->glDrawElements(GL_TRIANGLES, m_gridElements * m_gridElements * 6, GL_UNSIGNED_INT, 0);
break;
}
f->glPolygonOffset(0, 0);
f->glDisable(GL_POLYGON_OFFSET_FILL);
if (style == SpectrumSettings::Outline)
{
// Draw the outline
program->setUniformValue(m_brightnessLoc, 1.5f);
m_index0Buf.bind();
f->glDrawElements(GL_LINES, m_gridElements * (m_gridElements+1) * 4, GL_UNSIGNED_INT, 0);
}
f->glDisableVertexAttribArray(attribute_coord2d);
// Need to do this, otherwise nothing else is drawn...
f->glBindBuffer(GL_ARRAY_BUFFER, 0);
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// If this is left enabled, channel markers aren't drawn properly
f->glDisable(GL_DEPTH_TEST);
program->release();
}
void GLShaderSpectrogram::cleanup()
{
if (!QOpenGLContext::currentContext()) {
return;
}
if (m_programShaded)
{
delete m_programShaded;
m_programShaded = nullptr;
}
if (m_programSimple)
{
delete m_programSimple;
m_programSimple = nullptr;
}
m_programForLocs = nullptr;
if (m_texture)
{
delete m_texture;
m_texture = nullptr;
}
if (m_textureId)
{
glDeleteTextures(1, &m_textureId);
m_textureId = 0;
}
if (m_colorMapTextureId)
{
glDeleteTextures(1, &m_colorMapTextureId);
m_colorMapTextureId = 0;
}
if (m_colorMapTexture)
{
delete m_colorMapTexture;
m_colorMapTexture = nullptr;
}
}
bool GLShaderSpectrogram::useImmutableStorage()
{
QOpenGLContext* ctx = QOpenGLContext::currentContext();
QSurfaceFormat sf = ctx->format();
if (sf.version() >= qMakePair(4, 2)
|| ctx->hasExtension(QByteArrayLiteral("GL_ARB_texture_storage"))
|| ctx->hasExtension(QByteArrayLiteral("GL_EXT_texture_storage")))
{
void (QOPENGLF_APIENTRYP glTexStorage2D)(
GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height);
glTexStorage2D = reinterpret_cast<void (QOPENGLF_APIENTRYP)(
GLenum, GLsizei, GLenum, GLsizei, GLsizei)>(ctx->getProcAddress("glTexStorage2D"));
int data = 0;
GLuint textureId;
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, &data);
GLenum err = glGetError();
glDeleteTextures(1, &textureId);
return err == GL_NO_ERROR;
}
return false;
}
void GLShaderSpectrogram::translateX(float distance)
{
m_translateX += distance;
}
void GLShaderSpectrogram::translateY(float distance)
{
m_translateY += distance;
}
void GLShaderSpectrogram::translateZ(float distance)
{
m_translateZ += distance;
}
void GLShaderSpectrogram::rotateX(float degrees)
{
m_rotX += degrees;
}
void GLShaderSpectrogram::rotateY(float degrees)
{
m_rotY += degrees;
}
void GLShaderSpectrogram::rotateZ(float degrees)
{
m_rotZ += degrees;
}
void GLShaderSpectrogram::setScaleX(float factor)
{
m_scaleX = factor;
}
void GLShaderSpectrogram::setScaleY(float factor)
{
m_scaleY = factor;
}
void GLShaderSpectrogram::setScaleZ(float factor)
{
m_scaleZ = factor;
}
void GLShaderSpectrogram::userScaleZ(float factor)
{
m_userScaleZ *= factor;
if ((m_userScaleZ < 0.1) && (factor < 1.0)) {
m_userScaleZ = 0.0;
} else if ((m_userScaleZ == 0.0) && (factor > 1.0)) {
m_userScaleZ = 0.1;
}
}
void GLShaderSpectrogram::reset()
{
// Don't reset m_scaleX, m_scaleY and m_scaleZ, m_aspectRatio
// as they are not set directly by user, but depend on window size
m_translateX = 0.0f;
m_translateY = 0.0f;
m_translateZ = 0.0f;
m_rotX = -45.0f;
m_rotY = 0.0f;
m_rotZ = 0.0f;
m_userScaleZ = 1.0f;
m_verticalAngle = 45.0f;
m_lightTranslateX = 0.0f;
m_lightTranslateY = 0.0f;
m_lightTranslateZ = 0.0f;
m_lightRotX = 0.0f;
m_lightRotY = 0.0f;
m_lightRotZ = 0.0f;
setPerspective();
}
void GLShaderSpectrogram::setAspectRatio(float aspectRatio)
{
m_aspectRatio = aspectRatio;
setPerspective();
}
void GLShaderSpectrogram::verticalAngle(float degrees)
{
m_verticalAngle += degrees;
m_verticalAngle = std::max(1.0f, m_verticalAngle);
m_verticalAngle = std::min(179.0f, m_verticalAngle);
setPerspective();
}
void GLShaderSpectrogram::setPerspective()
{
m_perspective.setToIdentity();
m_perspective.perspective(m_verticalAngle, m_aspectRatio, 0.1, 7.0);
}
void GLShaderSpectrogram::lightTranslateX(float distance)
{
m_lightTranslateX += distance;
}
void GLShaderSpectrogram::lightTranslateY(float distance)
{
m_lightTranslateY += distance;
}
void GLShaderSpectrogram::lightTranslateZ(float distance)
{
m_lightTranslateZ += distance;
}
void GLShaderSpectrogram::lightRotateX(float degrees)
{
m_lightRotX += degrees;
}
void GLShaderSpectrogram::lightRotateY(float degrees)
{
m_lightRotY += degrees;
}
void GLShaderSpectrogram::lightRotateZ(float degrees)
{
m_lightRotZ += degrees;
}
void GLShaderSpectrogram::applyScaleRotate(QMatrix4x4 &matrix)
{
// As above, this is in reverse
matrix.translate(m_translateX, m_translateY, m_translateZ);
matrix.rotate(m_rotX, 1.0f, 0.0f, 0.0f);
matrix.rotate(m_rotY, 0.0f, 1.0f, 0.0f);
matrix.rotate(m_rotZ, 0.0f, 0.0f, 1.0f);
matrix.scale(m_scaleX, m_scaleY, m_scaleZ * m_userScaleZ);
}
void GLShaderSpectrogram::applyPerspective(QMatrix4x4 &matrix)
{
matrix = m_perspective * matrix;
}
// The clamp is to prevent old data affecting new data (And vice versa),
// which can happen where the texture repeats - might be a better way to do it
const QString GLShaderSpectrogram::m_vertexShader = QString(
"attribute vec2 coord2d;\n"
"varying vec4 coord;\n"
"varying float lightDistance;\n"
"uniform mat4 textureTransform;\n"
"uniform mat4 vertexTransform;\n"
"uniform sampler2D texture;\n"
"uniform float limit;\n"
"uniform vec3 lightPos;\n"
"void main(void) {\n"
" coord = textureTransform * vec4(clamp(coord2d, limit, 1.0-limit), 0, 1);\n"
" coord.z = (texture2D(texture, coord.xy).r);\n"
" gl_Position = vertexTransform * vec4(coord2d, coord.z, 1);\n"
" lightDistance = length(lightPos - gl_Position.xyz);\n"
"}\n"
);
// We need to use a geometry shader to calculate the normals, as they are only
// determined after z has been calculated for the verticies in the vertex shader
const QString GLShaderSpectrogram::m_geometryShader = QString(
"#version 330\n"
"layout(triangles) in;\n"
"layout(triangle_strip, max_vertices=3) out;\n"
"in vec4 coord[];\n"
"in float lightDistance[];\n"
"out vec4 coord2;\n"
"out vec3 normal;\n"
"out float lightDistance2;\n"
"void main(void) {\n"
" vec3 a = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz;\n"
" vec3 b = (gl_in[2].gl_Position - gl_in[0].gl_Position).xyz;\n"
" vec3 N = normalize(cross(b, a));\n"
" for(int i=0; i < gl_in.length(); ++i)\n"
" {\n"
" gl_Position = gl_in[i].gl_Position;\n"
" normal = N;\n"
" coord2 = coord[i];\n"
" lightDistance2 = lightDistance[i];\n"
" EmitVertex( );\n"
" }\n"
" EndPrimitive( );\n"
"}\n"
);
const QString GLShaderSpectrogram::m_fragmentShaderShaded = QString(
"varying vec4 coord2;\n"
"varying vec3 normal;\n"
"varying float lightDistance2;\n"
"uniform sampler1D colorMap;\n"
"uniform vec3 lightDir;\n"
"void main(void) {\n"
" float factor;\n"
" if (gl_FrontFacing)\n"
" factor = 1.0;\n"
" else\n"
" factor = 0.5;\n"
" float ambient = 0.4;\n"
" vec3 color;\n"
" color.r = texture1D(colorMap, coord2.z).r;\n"
" color.g = texture1D(colorMap, coord2.z).g;\n"
" color.b = texture1D(colorMap, coord2.z).b;\n"
" float cosTheta = max(0.0, dot(normal, lightDir));\n"
" float d2 = max(1.0, lightDistance2*lightDistance2);\n"
" vec3 relection = (ambient * color + color * cosTheta / d2) * factor;\n"
" gl_FragColor[0] = relection.r;\n"
" gl_FragColor[1] = relection.g;\n"
" gl_FragColor[2] = relection.b;\n"
" gl_FragColor[3] = 1.0;\n"
"}\n"
);
const QString GLShaderSpectrogram::m_fragmentShaderSimple = QString(
"varying vec4 coord;\n"
"uniform float brightness;\n"
"uniform sampler1D colorMap;\n"
"void main(void) {\n"
" float factor;\n"
" if (gl_FrontFacing)\n"
" factor = 1.0;\n"
" else\n"
" factor = 0.5;\n"
" gl_FragColor[0] = texture1D(colorMap, coord.z).r * brightness * factor;\n"
" gl_FragColor[1] = texture1D(colorMap, coord.z).g * brightness * factor;\n"
" gl_FragColor[2] = texture1D(colorMap, coord.z).b * brightness * factor;\n"
" gl_FragColor[3] = 1.0;\n"
"}\n"
);

View File

@ -0,0 +1,128 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB. //
// Copyright (C) 2022 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GUI_GLSHADERSPECTROGRAM_H_
#define INCLUDE_GUI_GLSHADERSPECTROGRAM_H_
#include <QString>
#include <QOpenGLTexture>
#include <QOpenGLFunctions>
#include "export.h"
#include "dsp/spectrumsettings.h"
class QOpenGLShaderProgram;
class QMatrix4x4;
class QImage;
class SDRGUI_API GLShaderSpectrogram : protected QOpenGLFunctions
{
public:
GLShaderSpectrogram();
~GLShaderSpectrogram();
void initializeGL();
void initColorMapTexture(const QString &colorMapName);
void initTexture(const QImage& image);
void subTexture(int xOffset, int yOffset, int width, int height, const void *pixels);
void drawSurface(SpectrumSettings::SpectrogramStyle style, float textureOffset, bool invert);
void cleanup();
void translateX(float distance);
void translateY(float distance);
void translateZ(float distance);
void rotateX(float degrees);
void rotateY(float degrees);
void rotateZ(float degrees);
void setScaleX(float factor);
void setScaleY(float factor);
void setScaleZ(float factor);
void userScaleZ(float factor);
void reset();
void setAspectRatio(float aspectRatio);
void verticalAngle(float degrees);
void lightTranslateX(float distance);
void lightTranslateY(float distance);
void lightTranslateZ(float distance);
void lightRotateX(float degrees);
void lightRotateY(float degrees);
void lightRotateZ(float degrees);
void applyScaleRotate(QMatrix4x4 &matrix);
void applyPerspective(QMatrix4x4 &matrix);
private:
void initColorMapTextureMutable(const QString &colorMapName);
void initColorMapTextureImmutable(const QString &colorMapName);
void initTextureImmutable(const QImage& image);
void subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels);
void initTextureMutable(const QImage& image);
void subTextureMutable(int xOffset, int yOffset, int width, int height, const void *pixels);
bool useImmutableStorage();
void initGrid(int elements);
void setPerspective();
QOpenGLShaderProgram *m_programShaded;
QOpenGLShaderProgram *m_programSimple;
QOpenGLTexture *m_texture;
unsigned int m_textureId;
QOpenGLTexture *m_colorMapTexture;
unsigned int m_colorMapTextureId;
QOpenGLShaderProgram *m_programForLocs; // Which program the locations are for
int m_textureTransformLoc;
int m_vertexTransformLoc;
int m_textureLoc;
int m_limitLoc;
int m_brightnessLoc;
int m_colorMapLoc;
int m_lightDirLoc;
int m_lightPosLoc;
bool m_useImmutableStorage;
static const QString m_vertexShader;
static const QString m_geometryShader;
static const QString m_fragmentShaderShaded;
static const QString m_fragmentShaderSimple;
QOpenGLBuffer m_vertexBuf;
QOpenGLBuffer m_index0Buf;
QOpenGLBuffer m_index1Buf;
float m_translateX;
float m_translateY;
float m_translateZ;
float m_rotX;
float m_rotY;
float m_rotZ;
float m_scaleX;
float m_scaleY;
float m_scaleZ;
float m_userScaleZ;
float m_verticalAngle;
float m_aspectRatio;
float m_lightTranslateX;
float m_lightTranslateY;
float m_lightTranslateZ;
float m_lightRotX;
float m_lightRotY;
float m_lightRotZ;
QMatrix4x4 m_perspective;
int m_gridElements;
};
#endif /* INCLUDE_GUI_GLSHADERSPECTROGRAM_H_ */

View File

@ -144,16 +144,16 @@ void GLShaderTextured::subTextureMutable(int xOffset, int yOffset, int width, in
glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
void GLShaderTextured::drawSurface(const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
void GLShaderTextured::drawSurface(const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
{
if (m_useImmutableStorage) {
draw(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices);
draw(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices, nbComponents);
} else {
drawMutable(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices);
drawMutable(GL_TRIANGLE_FAN, transformMatrix, textureCoords, vertices, nbVertices, nbComponents);
}
}
void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
{
if (!m_texture)
{
@ -167,7 +167,7 @@ void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix
m_texture->bind();
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
f->glEnableVertexAttribArray(0); // vertex
f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
f->glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
f->glEnableVertexAttribArray(1); // texture coordinates
f->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
f->glDrawArrays(mode, 0, nbVertices);
@ -175,7 +175,7 @@ void GLShaderTextured::draw(unsigned int mode, const QMatrix4x4& transformMatrix
m_program->release();
}
void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices)
void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents)
{
if (!m_textureId)
{
@ -188,7 +188,7 @@ void GLShaderTextured::drawMutable(unsigned int mode, const QMatrix4x4& transfor
glBindTexture(GL_TEXTURE_2D, m_textureId);
m_program->setUniformValue(m_textureLoc, 0); // Use texture unit 0 which magically contains our texture
glEnableVertexAttribArray(0); // vertex
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(0, nbComponents, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(1); // texture coordinates
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, textureCoords);
glDrawArrays(mode, 0, nbVertices);

View File

@ -41,12 +41,12 @@ public:
void initializeGL();
void initTexture(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);
void subTexture(int xOffset, int yOffset, int width, int height, const void *pixels);
void drawSurface(const QMatrix4x4& transformMatrix, GLfloat* textureCoords, GLfloat *vertices, int nbVertices);
void drawSurface(const QMatrix4x4& transformMatrix, GLfloat* textureCoords, GLfloat *vertices, int nbVertices, int nbComponents=2);
void cleanup();
private:
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices);
void drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices);
void draw(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents);
void drawMutable(unsigned int mode, const QMatrix4x4& transformMatrix, GLfloat *textureCoords, GLfloat *vertices, int nbVertices, int nbComponents);
void initTextureImmutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);
void subTextureImmutable(int xOffset, int yOffset, int width, int height, const void *pixels);
void initTextureMutable(const QImage& image, QOpenGLTexture::WrapMode wrapMode = QOpenGLTexture::Repeat);

View File

@ -23,8 +23,10 @@
#include <QOpenGLFunctions>
#include <QPainter>
#include <QFontDatabase>
#include "maincore.h"
#include "dsp/spectrumvis.h"
#include "gui/glspectrum.h"
#include "settings/mainsettings.h"
#include "util/messagequeue.h"
#include "util/db.h"
@ -79,6 +81,16 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
m_displayWaterfall(true),
m_ssbSpectrum(false),
m_lsbDisplay(false),
m_3DSpectrogramBuffer(nullptr),
m_3DSpectrogramBufferPos(0),
m_3DSpectrogramTextureHeight(-1),
m_3DSpectrogramTexturePos(0),
m_display3DSpectrogram(false),
m_rotate3DSpectrogram(false),
m_pan3DSpectrogram(false),
m_scaleZ3DSpectrogram(false),
m_3DSpectrogramStyle(SpectrumSettings::Outline),
m_3DSpectrogramColorMap("Angel"),
m_histogramBuffer(nullptr),
m_histogram(nullptr),
m_displayHistogram(true),
@ -91,8 +103,18 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
m_calibrationGain(1.0),
m_calibrationShiftdB(0.0),
m_calibrationInterpMode(SpectrumSettings::CalibInterpLinear),
m_messageQueueToGUI(nullptr)
m_messageQueueToGUI(nullptr),
m_openGLLogger(nullptr)
{
// Enable multisampling anti-aliasing (MSAA)
int multisamples = MainCore::instance()->getSettings().getMultisampling();
if (multisamples > 0)
{
QSurfaceFormat format;
format.setSamples(multisamples);
setFormat(format);
}
setObjectName("GLSpectrum");
setAutoFillBackground(false);
setAttribute(Qt::WA_OpaquePaintEvent, true);
@ -179,6 +201,10 @@ GLSpectrum::GLSpectrum(QWidget* parent) :
m_timer.setTimerType(Qt::PreciseTimer);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.start(m_fpsPeriodMs);
// Handle KeyEvents
setFocusPolicy(Qt::StrongFocus);
installEventFilter(this);
}
GLSpectrum::~GLSpectrum()
@ -191,6 +217,12 @@ GLSpectrum::~GLSpectrum()
m_waterfallBuffer = nullptr;
}
if (m_3DSpectrogramBuffer)
{
delete m_3DSpectrogramBuffer;
m_3DSpectrogramBuffer = nullptr;
}
if (m_histogramBuffer)
{
delete m_histogramBuffer;
@ -202,6 +234,12 @@ GLSpectrum::~GLSpectrum()
delete[] m_histogram;
m_histogram = nullptr;
}
if (m_openGLLogger)
{
delete m_openGLLogger;
m_openGLLogger = nullptr;
}
}
void GLSpectrum::setCenterFrequency(qint64 frequency)
@ -296,6 +334,31 @@ void GLSpectrum::setDisplayWaterfall(bool display)
update();
}
void GLSpectrum::setDisplay3DSpectrogram(bool display)
{
m_mutex.lock();
m_display3DSpectrogram = display;
m_changesPending = true;
stopDrag();
m_mutex.unlock();
update();
}
void GLSpectrum::set3DSpectrogramStyle(SpectrumSettings::SpectrogramStyle style)
{
m_3DSpectrogramStyle = style;
update();
}
void GLSpectrum::set3DSpectrogramColorMap(const QString &colorMap)
{
m_mutex.lock();
m_3DSpectrogramColorMap = colorMap;
m_changesPending = true;
m_mutex.unlock();
update();
}
void GLSpectrum::setSsbSpectrum(bool ssbSpectrum)
{
m_ssbSpectrum = ssbSpectrum;
@ -537,6 +600,7 @@ void GLSpectrum::newSpectrum(const Real *spectrum, int nbBins, int fftSize)
}
updateWaterfall(spectrum);
update3DSpectrogram(spectrum);
updateHistogram(spectrum);
}
@ -563,6 +627,29 @@ void GLSpectrum::updateWaterfall(const Real *spectrum)
}
}
void GLSpectrum::update3DSpectrogram(const Real *spectrum)
{
if (m_3DSpectrogramBufferPos < m_3DSpectrogramBuffer->height())
{
quint8* pix = (quint8*)m_3DSpectrogramBuffer->scanLine(m_3DSpectrogramBufferPos);
for (int i = 0; i < m_nbBins; i++)
{
int v = (int)((spectrum[i] - m_referenceLevel) * 2.4 * 100.0 / m_powerRange + 240.0);
if (v > 255) {
v = 255;
} else if (v < 0) {
v = 0;
}
*pix++ = v;
}
m_3DSpectrogramBufferPos++;
}
}
void GLSpectrum::updateHistogram(const Real *spectrum)
{
quint8* b = m_histogram;
@ -691,6 +778,27 @@ void GLSpectrum::initializeGL()
else {
qDebug() << "GLSpectrum::initializeGL: current context is invalid";
}
if ( (MainCore::instance()->getSettings().getConsoleMinLogLevel() <= QtDebugMsg)
|| (MainCore::instance()->getSettings().getFileMinLogLevel() <= QtDebugMsg))
{
// Enable OpenGL debugging
QSurfaceFormat format = glCurrentContext->format();
format.setOption(QSurfaceFormat::DebugContext);
glCurrentContext->setFormat(format);
if (glCurrentContext->hasExtension(QByteArrayLiteral("GL_KHR_debug")))
{
m_openGLLogger = new QOpenGLDebugLogger(this);
m_openGLLogger->initialize();
connect(m_openGLLogger, &QOpenGLDebugLogger::messageLogged, this, &GLSpectrum::openGLDebug);
m_openGLLogger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
}
else
{
qDebug() << "GLSpectrum::initializeGL: GL_KHR_debug not available";
}
}
}
else
{
@ -711,6 +819,14 @@ void GLSpectrum::initializeGL()
m_glShaderHistogram.initializeGL();
m_glShaderTextOverlay.initializeGL();
m_glShaderInfo.initializeGL();
m_glShaderSpectrogram.initializeGL();
m_glShaderSpectrogramTimeScale.initializeGL();
m_glShaderSpectrogramPowerScale.initializeGL();
}
void GLSpectrum::openGLDebug(const QOpenGLDebugMessage &debugMessage)
{
qDebug() << "GLSpectrum::openGLDebug: " << debugMessage;
}
void GLSpectrum::resizeGL(int width, int height)
@ -753,11 +869,43 @@ void GLSpectrum::paintGL()
QOpenGLFunctions *glFunctions = QOpenGLContext::currentContext()->functions();
glFunctions->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glFunctions->glClear(GL_COLOR_BUFFER_BIT);
glFunctions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// paint waterfall
if (m_displayWaterfall)
QMatrix4x4 spectrogramGridMatrix;
spectrogramGridMatrix.translate(0.0f, 0.0f, -1.65f);
m_glShaderSpectrogram.applyScaleRotate(spectrogramGridMatrix);
spectrogramGridMatrix.translate(-0.5f, -0.5f, 0.0f);
m_glShaderSpectrogram.applyPerspective(spectrogramGridMatrix);
if (m_display3DSpectrogram)
{
// paint 3D spectrogram
if (m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos < m_3DSpectrogramTextureHeight)
{
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, m_3DSpectrogramBufferPos, m_3DSpectrogramBuffer->scanLine(0));
m_3DSpectrogramTexturePos += m_3DSpectrogramBufferPos;
}
else
{
int breakLine = m_3DSpectrogramTextureHeight - m_3DSpectrogramTexturePos;
int linesLeft = m_3DSpectrogramTexturePos + m_3DSpectrogramBufferPos - m_3DSpectrogramTextureHeight;
m_glShaderSpectrogram.subTexture(0, m_3DSpectrogramTexturePos, m_nbBins, breakLine, m_3DSpectrogramBuffer->scanLine(0));
m_glShaderSpectrogram.subTexture(0, 0, m_nbBins, linesLeft, m_3DSpectrogramBuffer->scanLine(breakLine));
m_3DSpectrogramTexturePos = linesLeft;
}
m_3DSpectrogramBufferPos = 0;
float prop_y = m_3DSpectrogramTexturePos / (m_3DSpectrogramTextureHeight - 1.0);
// Temporarily reduce viewport to waterfall area so anything outside is clipped
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
m_glShaderSpectrogram.drawSurface(m_3DSpectrogramStyle, prop_y, m_invertedWaterfall);
glFunctions->glViewport(0, 0, width(), height());
}
else if (m_displayWaterfall)
{
// paint 2D waterfall
{
GLfloat vtx1[] = {
0, m_invertedWaterfall ? 0.0f : 1.0f,
@ -960,7 +1108,7 @@ void GLSpectrum::paintGL()
}
// paint frequency scale
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram )
if (m_displayWaterfall || m_displayMaxHold || m_displayCurrent || m_displayHistogram)
{
{
GLfloat vtx1[] = {
@ -1015,6 +1163,74 @@ void GLSpectrum::paintGL()
}
}
// paint 3D spectrogram scales
if (m_display3DSpectrogram && m_displayGrid)
{
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
{
float l = m_spectrogramTimePixmap.width() / (float) width();
float r = m_rightMargin / (float) width();
float h = m_frequencyPixmap.height() / (float) m_waterfallHeight;
GLfloat vtx1[] = {
-l, -h,
1+r, -h,
1+r, 0,
-l, 0
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderFrequencyScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
float w = m_spectrogramTimePixmap.width() / (float) width();
float h = (m_bottomMargin/2) / (float) m_waterfallHeight; // m_bottomMargin is fm.ascent
GLfloat vtx1[] = {
-w, 0.0-h,
0, 0.0-h,
0, 1.0+h,
-w, 1.0+h
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramTimeScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4);
}
{
float w = m_spectrogramPowerPixmap.width() / (float) width();
float h = m_topMargin / (float) m_spectrogramPowerPixmap.height();
GLfloat vtx1[] = {
-w, 1.0, 0.0,
0, 1.0, 0.0,
0, 1.0, 1.0+h,
-w, 1.0, 1.0+h,
};
GLfloat tex1[] = {
0, 1,
1, 1,
1, 0,
0, 0
};
m_glShaderSpectrogramPowerScale.drawSurface(spectrogramGridMatrix, tex1, vtx1, 4, 3);
}
glFunctions->glViewport(0, 0, width(), height());
}
// paint max hold lines on top of histogram
if (m_displayMaxHold)
{
@ -1154,6 +1370,128 @@ void GLSpectrum::paintGL()
}
}
// paint 3D spectrogram grid - this is drawn on top of signal, so that appears slightly transparent
// x-axis is freq, y time and z power
if (m_displayGrid && m_display3DSpectrogram)
{
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
glFunctions->glViewport(0, m_3DSpectrogramBottom, width(), m_waterfallHeight);
tickList = &m_powerScale.getTickList();
{
GLfloat *q3 = m_q3TickPower.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_powerScale.getSize();
q3[6*effectiveTicks] = 0.0;
q3[6*effectiveTicks+1] = 1.0;
q3[6*effectiveTicks+2] = y;
q3[6*effectiveTicks+3] = 1.0;
q3[6*effectiveTicks+4] = 1.0;
q3[6*effectiveTicks+5] = y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
}
tickList = &m_timeScale.getTickList();
{
GLfloat *q3 = m_q3TickTime.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float y = tick->pos / m_timeScale.getSize();
q3[4*effectiveTicks] = 0.0;
q3[4*effectiveTicks+1] = 1.0 - y;
q3[4*effectiveTicks+2] = 1.0;
q3[4*effectiveTicks+3] = 1.0 - y;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
}
tickList = &m_frequencyScale.getTickList();
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[4*effectiveTicks] = x;
q3[4*effectiveTicks+1] = -0.0;
q3[4*effectiveTicks+2] = x;
q3[4*effectiveTicks+3] = 1.0;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks);
}
{
GLfloat *q3 = m_q3TickFrequency.m_array;
int effectiveTicks = 0;
for (int i= 0; i < tickList->count(); i++)
{
tick = &(*tickList)[i];
if (tick->major)
{
if (tick->textSize > 0)
{
float x = tick->pos / m_frequencyScale.getSize();
q3[6*effectiveTicks] = x;
q3[6*effectiveTicks+1] = 1.0;
q3[6*effectiveTicks+2] = 0.0;
q3[6*effectiveTicks+3] = x;
q3[6*effectiveTicks+4] = 1.0;
q3[6*effectiveTicks+5] = 1.0;
effectiveTicks++;
}
}
}
QVector4D color(1.0f, 1.0f, 1.0f, (float) m_displayGridIntensity / 100.0f);
m_glShaderSimple.drawSegments(spectrogramGridMatrix, color, q3, 2*effectiveTicks, 3);
}
glFunctions->glViewport(0, 0, width(), height());
}
// paint histogram grid
if ((m_displayHistogram || m_displayMaxHold || m_displayCurrent) && (m_displayGrid))
{
@ -1566,7 +1904,7 @@ void GLSpectrum::applyChanges()
m_rightMargin = fm.horizontalAdvance("000");
// displays both histogram and waterfall
if (m_displayWaterfall && (m_displayHistogram | m_displayMaxHold | m_displayCurrent))
if ((m_displayWaterfall || m_display3DSpectrogram) && (m_displayHistogram | m_displayMaxHold | m_displayCurrent))
{
m_waterfallHeight = height() * m_waterfallShare - 1;
@ -1611,24 +1949,9 @@ void GLSpectrum::applyChanges()
m_timeScale.setRange(Unit::Time, 0, 1);
}
m_powerScale.setSize(m_histogramHeight);
if (m_linear)
{
Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
}
else
{
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
}
m_leftMargin = m_timeScale.getScaleWidth();
if (m_powerScale.getScaleWidth() > m_leftMargin) {
m_leftMargin = m_powerScale.getScaleWidth();
}
setPowerScale(m_histogramHeight);
m_leftMargin += 2 * M;
@ -1688,8 +2011,8 @@ void GLSpectrum::applyChanges()
-2.0f
);
}
// displays waterfall only
else if (m_displayWaterfall)
// displays waterfall/3D spectrogram only
else if (m_displayWaterfall || m_display3DSpectrogram)
{
m_histogramHeight = 0;
histogramTop = 0;
@ -1725,6 +2048,9 @@ void GLSpectrum::applyChanges()
}
m_leftMargin = m_timeScale.getScaleWidth();
setPowerScale((height() - m_topMargin - m_bottomMargin) / 2.0);
m_leftMargin += 2 * M;
setFrequencyScale();
@ -1772,10 +2098,10 @@ void GLSpectrum::applyChanges()
m_waterfallHeight = 0;
m_histogramHeight = height() - m_topMargin - m_frequencyScaleHeight;
m_powerScale.setSize(m_histogramHeight);
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
m_leftMargin = m_powerScale.getScaleWidth();
m_leftMargin = 0;
setPowerScale(m_histogramHeight);
m_leftMargin += 2 * M;
setFrequencyScale();
@ -1830,6 +2156,9 @@ void GLSpectrum::applyChanges()
m_waterfallHeight = 0;
}
m_glShaderSpectrogram.setScaleX(((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight));
m_glShaderSpectrogram.setScaleZ((m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4) / (float)(width() - m_leftMargin - m_rightMargin));
// bounding boxes
m_frequencyScaleRect = QRect(
0,
@ -1876,6 +2205,13 @@ void GLSpectrum::applyChanges()
);
}
m_glShaderSpectrogram.setAspectRatio((width() - m_leftMargin - m_rightMargin) / (float)m_waterfallHeight);
m_3DSpectrogramBottom = m_bottomMargin;
if (!m_invertedWaterfall) {
m_3DSpectrogramBottom += m_histogramHeight + m_frequencyScaleHeight + 1;
}
// channel overlays
int64_t centerFrequency;
int frequencySpan;
@ -2034,7 +2370,7 @@ void GLSpectrum::applyChanges()
// prepare left scales (time and power)
{
m_leftMarginPixmap = QPixmap(m_leftMargin - 1, height());
m_leftMarginPixmap.fill(Qt::black);
m_leftMarginPixmap.fill(Qt::transparent);
{
QPainter painter(&m_leftMarginPixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
@ -2066,7 +2402,7 @@ void GLSpectrum::applyChanges()
m_glShaderLeftScale.initTexture(m_leftMarginPixmap.toImage());
}
// prepare frequency scale
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent){
if (m_displayWaterfall || m_display3DSpectrogram || m_displayHistogram || m_displayMaxHold || m_displayCurrent) {
m_frequencyPixmap = QPixmap(width(), m_frequencyScaleHeight);
m_frequencyPixmap.fill(Qt::transparent);
{
@ -2135,6 +2471,55 @@ void GLSpectrum::applyChanges()
m_glShaderFrequencyScale.initTexture(m_frequencyPixmap.toImage());
}
// prepare left scale for spectrogram (time)
{
m_spectrogramTimePixmap = QPixmap(m_leftMargin - 1, fm.ascent() + m_waterfallHeight);
m_spectrogramTimePixmap.fill(Qt::transparent);
{
QPainter painter(&m_spectrogramTimePixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
if (m_display3DSpectrogram) {
tickList = &m_timeScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, fm.height() + tick->textPos), tick->text);
}
}
}
}
m_glShaderSpectrogramTimeScale.initTexture(m_spectrogramTimePixmap.toImage());
}
// prepare vertical scale for spectrogram (power)
{
int h = m_histogramHeight != 0 ? m_histogramHeight : m_waterfallHeight / 4;
m_spectrogramPowerPixmap = QPixmap(m_leftMargin - 1, m_topMargin + h);
m_spectrogramPowerPixmap.fill(Qt::transparent);
{
QPainter painter(&m_spectrogramPowerPixmap);
painter.setPen(QColor(0xf0, 0xf0, 0xff));
painter.setFont(font());
const ScaleEngine::TickList* tickList;
const ScaleEngine::Tick* tick;
if (m_display3DSpectrogram) {
tickList = &m_powerScale.getTickList();
for (int i = 0; i < tickList->count(); i++) {
tick = &(*tickList)[i];
if (tick->major) {
if (tick->textSize > 0)
painter.drawText(QPointF(m_leftMargin - M - tick->textSize, m_topMargin + h - tick->textPos - 1), tick->text);
}
}
}
}
m_glShaderSpectrogramPowerScale.initTexture(m_spectrogramPowerPixmap.toImage());
}
// Top info line
m_glInfoBoxMatrix.setToIdentity();
@ -2188,7 +2573,18 @@ void GLSpectrum::applyChanges()
m_waterfallBuffer->fill(qRgb(0x00, 0x00, 0x00));
m_glShaderWaterfall.initTexture(*m_waterfallBuffer);
m_waterfallBufferPos = 0;
if (m_3DSpectrogramBuffer) {
delete m_3DSpectrogramBuffer;
}
m_3DSpectrogramBuffer = new QImage(m_nbBins, m_waterfallHeight, QImage::Format_Grayscale8);
m_3DSpectrogramBuffer->fill(qRgb(0x00, 0x00, 0x00));
m_glShaderSpectrogram.initTexture(*m_3DSpectrogramBuffer);
m_3DSpectrogramBufferPos = 0;
}
m_glShaderSpectrogram.initColorMapTexture(m_3DSpectrogramColorMap);
if (fftSizeChanged)
{
@ -2218,11 +2614,13 @@ void GLSpectrum::applyChanges()
{
m_waterfallTextureHeight = m_waterfallHeight;
m_waterfallTexturePos = 0;
m_3DSpectrogramTextureHeight = m_waterfallHeight;
m_3DSpectrogramTexturePos = 0;
}
m_q3TickTime.allocate(4*m_timeScale.getTickList().count());
m_q3TickFrequency.allocate(4*m_frequencyScale.getTickList().count());
m_q3TickPower.allocate(4*m_powerScale.getTickList().count());
m_q3TickPower.allocate(6*m_powerScale.getTickList().count()); // 6 as we need 3d points for 3D spectrogram
updateHistogramMarkers();
updateWaterfallMarkers();
updateSortedAnnotationMarkers();
@ -2461,6 +2859,37 @@ void GLSpectrum::updateCalibrationPoints()
void GLSpectrum::mouseMoveEvent(QMouseEvent* event)
{
if (m_rotate3DSpectrogram)
{
// Rotate 3D Spectrogram
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.rotateZ(-delta.x()/2.0f);
m_glShaderSpectrogram.rotateX(-delta.y()/2.0f);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_pan3DSpectrogram)
{
// Pan 3D Spectrogram
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.translateX(-delta.x()/2.0f/500.0f);
m_glShaderSpectrogram.translateY(delta.y()/2.0f/500.0f);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_scaleZ3DSpectrogram)
{
// Scale 3D Spectrogram in Z dimension
QPointF delta = m_mousePrevLocalPos - event->localPos();
m_mousePrevLocalPos = event->localPos();
m_glShaderSpectrogram.userScaleZ(1.0+(float)delta.y()/20.0);
repaint(); // Force repaint in case acquisition is stopped
return;
}
if (m_displayWaterfall || m_displayHistogram || m_displayMaxHold || m_displayCurrent)
{
if (m_frequencyScaleRect.contains(event->pos()))
@ -2573,6 +3002,20 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
{
const QPointF& ep = event->localPos();
if ((event->button() == Qt::MiddleButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
m_pan3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
if ((event->button() == Qt::RightButton) && m_display3DSpectrogram && pointInWaterfallOrSpectrogram(ep))
{
m_scaleZ3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
if (event->button() == Qt::RightButton)
{
QPointF pHis = ep;
@ -2681,7 +3124,7 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
frequency = m_frequencyScale.getRangeMin() + pWat.x()*m_frequencyScale.getRange();
float time = m_timeScale.getRangeMin() + pWat.y()*m_timeScale.getRange();
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1))
if ((pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1) && !m_display3DSpectrogram)
{
if (m_waterfallMarkers.size() < SpectrumWaterfallMarker::m_maxNbOfMarkers)
{
@ -2727,6 +3170,16 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
{
frequencyPan(event);
}
else if (m_display3DSpectrogram)
{
// Detect click and drag to rotate 3D spectrogram
if (pointInWaterfallOrSpectrogram(ep))
{
m_rotate3DSpectrogram = true;
m_mousePrevLocalPos = ep;
return;
}
}
if ((m_markersDisplay == SpectrumSettings::MarkersDisplayAnnotations) &&
(ep.y() <= m_histogramRect.top()*height() + m_annotationMarkerHeight + 2.0f))
@ -2794,6 +3247,9 @@ void GLSpectrum::mousePressEvent(QMouseEvent* event)
void GLSpectrum::mouseReleaseEvent(QMouseEvent*)
{
m_pan3DSpectrogram = false;
m_rotate3DSpectrogram = false;
m_scaleZ3DSpectrogram = false;
if (m_cursorState == CSSplitterMoving)
{
releaseMouse();
@ -2808,12 +3264,32 @@ void GLSpectrum::mouseReleaseEvent(QMouseEvent*)
void GLSpectrum::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier) {
channelMarkerMove(event, 100);
} else if (event->modifiers() & Qt::ControlModifier) {
channelMarkerMove(event, 10);
} else {
channelMarkerMove(event, 1);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
const QPointF& ep = event->position();
#else
const QPointF& ep = event->pos();
#endif
if (pointInWaterfallOrSpectrogram(ep))
{
// Scale 3D spectrogram when mouse wheel moved
// Some mice use delta in steps of 120 for 15 degrees
// for one step of mouse wheel
// Other mice/trackpads use smaller values
int delta = event->angleDelta().y();
if (delta != 0) {
m_glShaderSpectrogram.verticalAngle(-5.0*delta/120.0);
}
repaint(); // Force repaint in case acquisition is stopped
}
else
{
if (event->modifiers() & Qt::ShiftModifier) {
channelMarkerMove(event, 100);
} else if (event->modifiers() & Qt::ControlModifier) {
channelMarkerMove(event, 10);
} else {
channelMarkerMove(event, 1);
}
}
}
@ -2973,6 +3449,26 @@ void GLSpectrum::setFrequencyScale()
m_frequencyScale.setMakeOpposite(m_lsbDisplay);
}
void GLSpectrum::setPowerScale(int height)
{
m_powerScale.setSize(height);
if (m_linear)
{
Real referenceLevel = m_useCalibration ? m_referenceLevel * m_calibrationGain : m_referenceLevel;
m_powerScale.setRange(Unit::Scientific, 0.0f, referenceLevel);
}
else
{
Real referenceLevel = m_useCalibration ? m_referenceLevel + m_calibrationShiftdB : m_referenceLevel;
m_powerScale.setRange(Unit::Decibel, referenceLevel - m_powerRange, referenceLevel);
}
if (m_powerScale.getScaleWidth() > m_leftMargin) {
m_leftMargin = m_powerScale.getScaleWidth();
}
}
void GLSpectrum::getFrequencyZoom(int64_t& centerFrequency, int& frequencySpan)
{
frequencySpan = (m_frequencyZoomFactor == 1) ?
@ -3033,6 +3529,17 @@ void GLSpectrum::channelMarkerMove(QWheelEvent *event, int mul)
zoom(event);
}
// Return if specified point is within the bounds of the waterfall / 3D spectrogram screen area
bool GLSpectrum::pointInWaterfallOrSpectrogram(const QPointF &point) const
{
// m_waterfallRect is normalised to [0,1]
QPointF pWat = point;
pWat.rx() = (point.x()/width() - m_waterfallRect.left()) / m_waterfallRect.width();
pWat.ry() = (point.y()/height() - m_waterfallRect.top()) / m_waterfallRect.height();
return (pWat.x() >= 0) && (pWat.x() <= 1) && (pWat.y() >= 0) && (pWat.y() <= 1);
}
void GLSpectrum::enterEvent(QEvent* event)
{
m_mouseInside = true;
@ -3109,6 +3616,9 @@ void GLSpectrum::cleanup()
m_glShaderWaterfall.cleanup();
m_glShaderTextOverlay.cleanup();
m_glShaderInfo.cleanup();
m_glShaderSpectrogram.cleanup();
m_glShaderSpectrogramTimeScale.cleanup();
m_glShaderSpectrogramPowerScale.cleanup();
//doneCurrent();
}
@ -3268,3 +3778,88 @@ void GLSpectrum::formatTextInfo(QString& info)
info.append(tr("SP:%1 ").arg(displayScaled(frequencySpan, 'f', 3, true)));
}
}
bool GLSpectrum::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch (keyEvent->key())
{
case Qt::Key_Up:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateX(-5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateY(0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateY(0.05);
} else {
m_glShaderSpectrogram.rotateX(-5.0f);
}
break;
case Qt::Key_Down:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateX(5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateY(-0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateY(-0.05);
} else {
m_glShaderSpectrogram.rotateX(5.0f);
}
break;
case Qt::Key_Left:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateZ(5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateX(-0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateX(-0.05);
} else {
m_glShaderSpectrogram.rotateZ(5.0f);
}
break;
case Qt::Key_Right:
if (keyEvent->modifiers() & Qt::ShiftModifier) {
m_glShaderSpectrogram.lightRotateZ(-5.0f);
} else if (keyEvent->modifiers() & Qt::AltModifier) {
m_glShaderSpectrogram.lightTranslateX(0.05);
} else if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.translateX(0.05);
} else {
m_glShaderSpectrogram.rotateZ(-5.0f);
}
break;
case Qt::Key_Plus:
if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.userScaleZ(1.1f);
} else {
m_glShaderSpectrogram.verticalAngle(-1.0f);
}
break;
case Qt::Key_Minus:
if (keyEvent->modifiers() & Qt::ControlModifier) {
m_glShaderSpectrogram.userScaleZ(0.9f);
} else {
m_glShaderSpectrogram.verticalAngle(1.0f);
}
break;
case Qt::Key_R:
m_glShaderSpectrogram.reset();
break;
case Qt::Key_F:
// Project straight down and scale to view, so it's a bit like 2D
m_glShaderSpectrogram.reset();
m_glShaderSpectrogram.rotateX(45.0f);
m_glShaderSpectrogram.verticalAngle(-9.0f);
m_glShaderSpectrogram.userScaleZ(0.0f);
break;
}
repaint(); // Force repaint in case acquisition is stopped
return true;
}
else
{
return QOpenGLWidget::eventFilter(object, event);
}
}

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

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