mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-27 02:09:14 -05:00
858 lines
28 KiB
C++
858 lines
28 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
|
|
// Copyright (C) 2016 Edouard Griffiths, F4EXB. //
|
|
// //
|
|
// 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>
|
|
#if defined(ANDROID)
|
|
#include <QOpenGLFunctions_ES2>
|
|
#else
|
|
#include <QOpenGLFunctions_3_3_Core>
|
|
#endif
|
|
#include <QOpenGLContext>
|
|
#include <QtOpenGL>
|
|
#include <QImage>
|
|
#include <QMatrix4x4>
|
|
#include <QVector4D>
|
|
#include <QDebug>
|
|
|
|
#ifdef ANDROID
|
|
#include <GLES3/gl3.h>
|
|
#endif
|
|
|
|
#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::Target2D);
|
|
m_colorMapTexture->setFormat(QOpenGLTexture::RGB32F);
|
|
m_colorMapTexture->setSize(256, 1);
|
|
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_2D, m_colorMapTextureId); // Use 2D texture as 1D not supported in OpenGL ES on ARM
|
|
GLfloat *colorMap = (GLfloat *)ColorMap::getColorMap(colorMapName);
|
|
if (colorMap) {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 1, 0, GL_RGB, GL_FLOAT, colorMap);
|
|
} else {
|
|
qDebug() << "GLShaderSpectrogram::initColorMapTextureMutable: colorMap " << colorMapName << " not supported";
|
|
}
|
|
|
|
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::initTexture(const QImage& image)
|
|
{
|
|
if (m_useImmutableStorage) {
|
|
initTextureImmutable(image);
|
|
} else {
|
|
initTextureMutable(image);
|
|
}
|
|
initGrid(image.width());
|
|
m_limit = 1.4f*1.0f/(float)image.height();
|
|
}
|
|
|
|
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_R8,
|
|
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_2D, m_colorMapTextureId);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
}
|
|
|
|
program->setUniformValue(m_dataTextureLoc, 0); // set uniform to texture unit?
|
|
program->setUniformValue(m_colorMapLoc, 1);
|
|
|
|
program->setUniformValue(m_limitLoc, m_limit);
|
|
|
|
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_vao;
|
|
m_vao = nullptr;
|
|
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 highp float lightDistance;\n"
|
|
"uniform mat4 textureTransform;\n"
|
|
"uniform mat4 vertexTransform;\n"
|
|
"uniform sampler2D dataTexture;\n"
|
|
"uniform highp 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 vertices 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 sampler2D 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, vec2(coord2.z, 0)).r;\n"
|
|
" color.g = texture(colorMap, vec2(coord2.z, 0)).g;\n"
|
|
" color.b = texture(colorMap, vec2(coord2.z, 0)).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 highp vec4 coord;\n"
|
|
"uniform highp float brightness;\n"
|
|
"uniform sampler2D colorMap;\n"
|
|
"void main(void) {\n"
|
|
" highp float factor;\n"
|
|
" if (gl_FrontFacing)\n"
|
|
" factor = 1.0;\n"
|
|
" else\n"
|
|
" factor = 0.5;\n"
|
|
" gl_FragColor[0] = texture2D(colorMap, vec2(coord.z, 0)).r * brightness * factor;\n"
|
|
" gl_FragColor[1] = texture2D(colorMap, vec2(coord.z, 0)).g * brightness * factor;\n"
|
|
" gl_FragColor[2] = texture2D(colorMap, vec2(coord.z, 0)).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 sampler2D 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, vec2(coord.z, 0)).r * brightness * factor;\n"
|
|
" fragColor[1] = texture(colorMap, vec2(coord.z, 0)).g * brightness * factor;\n"
|
|
" fragColor[2] = texture(colorMap, vec2(coord.z, 0)).b * brightness * factor;\n"
|
|
" fragColor[3] = 1.0;\n"
|
|
"}\n"
|
|
);
|