Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ SET(stellarium_lib_SRCS
core/StelOpenGLArray.cpp
core/StelHips.hpp
core/StelHips.cpp
core/TextureAverageComputer.hpp
core/TextureAverageComputer.cpp

${spout_SRCS}

Expand Down
1 change: 1 addition & 0 deletions src/StelMainView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "StelOpenGL.hpp"
#include "StelOpenGLArray.hpp"
#include "StelProjector.hpp"
#include "TextureAverageComputer.hpp"

#include <QDebug>
#include <QDir>
Expand Down
248 changes: 248 additions & 0 deletions src/core/TextureAverageComputer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/*
* Stellarium
* Copyright (C) 2023 Ruslan Kabatsayev
*
* 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; either version 2
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*/

#include "TextureAverageComputer.hpp"

#if !QT_CONFIG(opengles2) // This class uses glGetTexImage(), which is not supported in GLES2

#include <QOpenGLFunctions_3_3_Core>

namespace
{

int roundDownToClosestPowerOfTwo(const int x)
{
if(x==0) return 1;
int shift=0;
for(auto v=x;v;v>>=1)
++shift;
return 1<<(shift-1);
}

}

Vec4f TextureAverageComputer::getTextureAverageSimple(const GLuint texture, const int width, const int height)
{
// Get average value of the pixels as the value of the deepest mipmap level
gl.glActiveTexture(GL_TEXTURE0);
gl.glBindTexture(GL_TEXTURE_2D, texture);
gl.glGenerateMipmap(GL_TEXTURE_2D);

using namespace std;
// Formula from the glspec, "Mipmapping" subsection in section 3.8.11 Texture Minification
const auto totalMipmapLevels = 1+floor(log2(max(width,height)));
const auto deepestLevel=totalMipmapLevels-1;

#ifndef NDEBUG
// Sanity check
int deepestMipmapLevelWidth=-1, deepestMipmapLevelHeight=-1;
gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_WIDTH, &deepestMipmapLevelWidth);
gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_HEIGHT, &deepestMipmapLevelHeight);
assert(deepestMipmapLevelWidth==1);
assert(deepestMipmapLevelHeight==1);
#endif

Vec4f pixel;
gl.glGetTexImage(GL_TEXTURE_2D, deepestLevel, GL_RGBA, GL_FLOAT, &pixel[0]);
return pixel;
}

// Clobbers:
// GL_VERTEX_ARRAY_BINDING, GL_ACTIVE_TEXTURE, GL_TEXTURE_BINDING_2D, GL_CURRENT_PROGRAM,
// input texture's minification filter
Vec4f TextureAverageComputer::getTextureAverageWithWorkaround(const GLuint texture)
{
// Play it safe: we don't want to make the GPU struggle with very large textures
// if we happen to make them ~4 times larger. Instead round the dimensions down.
const auto potWidth = roundDownToClosestPowerOfTwo(npotWidth);
const auto potHeight = roundDownToClosestPowerOfTwo(npotHeight);

gl.glActiveTexture(GL_TEXTURE0);
gl.glBindTexture(GL_TEXTURE_2D, texture);
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

blitTexProgram->bind();

GLint oldViewport[4];
gl.glGetIntegerv(GL_VIEWPORT, oldViewport);
GLint oldFBO=-1;
gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldFBO);

gl.glBindFramebuffer(GL_FRAMEBUFFER, potFBO);
gl.glViewport(0,0,potWidth,potHeight);

gl.glBindVertexArray(vao);
gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
gl.glBindVertexArray(0);

gl.glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
gl.glBindFramebuffer(GL_FRAMEBUFFER, oldFBO);

blitTexProgram->release();

return getTextureAverageSimple(potTex, potWidth, potHeight);
}

Vec4f TextureAverageComputer::getTextureAverage(const GLuint texture)
{
if(workaroundNeeded)
return getTextureAverageWithWorkaround(texture);
return getTextureAverageSimple(texture, npotWidth, npotHeight);
}

void TextureAverageComputer::init()
{
GLuint texture = -1;
gl.glGenTextures(1, &texture);
assert(texture>0);
gl.glActiveTexture(GL_TEXTURE0);
gl.glBindTexture(GL_TEXTURE_2D, texture);

std::vector<Vec4f> data;
for(int n=0; n<10; ++n)
data.emplace_back(1,1,1,1);
for(int n=0; n<10; ++n)
data.emplace_back(1,1,1,0);
for(int n=0; n<10; ++n)
data.emplace_back(1,1,0,0);
for(int n=0; n<10; ++n)
data.emplace_back(1,0,0,0);

constexpr int width = 63;
for(int n=data.size(); n<width; ++n)
data.emplace_back(0,0,0,0);

gl.glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,data.size(),1,0,GL_RGBA,GL_FLOAT,&data[0][0]);
const auto mipmapAverage = getTextureAverageSimple(texture, width, 1);

const auto sum = std::accumulate(data.begin(), data.end(), Vec4f(0,0,0,0));
const auto trueAverage = sum / float(data.size());
qDebug().nospace() << "Test texture true average: "
<< trueAverage[0] << ", "
<< trueAverage[1] << ", "
<< trueAverage[2] << ", "
<< trueAverage[3];
qDebug().nospace() << "Test texture mipmap average: "
<< mipmapAverage[0] << ", "
<< mipmapAverage[1] << ", "
<< mipmapAverage[2] << ", "
<< mipmapAverage[3];

const auto diff = mipmapAverage - trueAverage;
using std::abs;
const auto maxDiff = std::max({abs(diff[0]),abs(diff[1]),abs(diff[2]),abs(diff[3])});
workaroundNeeded = maxDiff >= 2./255.;

if(workaroundNeeded)
{
qDebug() << "Mipmap average is unusable, will resize textures to "
"power-of-two size when average value is required.";
}
else
{
qDebug() << "Mipmap average works correctly";
}

gl.glBindTexture(GL_TEXTURE_2D, 0);
gl.glDeleteTextures(1, &texture);

inited = true;
}

// Clobbers: GL_TEXTURE_BINDING_2D, GL_VERTEX_ARRAY_BINDING, GL_ARRAY_BUFFER_BINDING
TextureAverageComputer::TextureAverageComputer(QOpenGLFunctions_3_3_Core& gl, const int texWidth, const int texHeight, const GLenum internalFormat)
: gl(gl)
, npotWidth(texWidth)
, npotHeight(texHeight)
{
if(!inited) init();
if(!workaroundNeeded) return;

GLint oldFBO=-1;
gl.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldFBO);

gl.glGenFramebuffers(1, &potFBO);
gl.glGenTextures(1, &potTex);
gl.glBindTexture(GL_TEXTURE_2D, potTex);
const auto potWidth = roundDownToClosestPowerOfTwo(npotWidth);
const auto potHeight = roundDownToClosestPowerOfTwo(npotHeight);
gl.glTexImage2D(GL_TEXTURE_2D,0,internalFormat,potWidth,potHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
gl.glBindTexture(GL_TEXTURE_2D,0);
gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER,potFBO);
gl.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,potTex,0);
[[maybe_unused]] const auto status=gl.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
assert(status==GL_FRAMEBUFFER_COMPLETE);
gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);

gl.glGenVertexArrays(1, &vao);
gl.glBindVertexArray(vao);
gl.glGenBuffers(1, &vbo);
gl.glBindBuffer(GL_ARRAY_BUFFER, vbo);
const GLfloat vertices[]=
{
-1, -1,
1, -1,
-1, 1,
1, 1,
};
gl.glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
constexpr GLuint attribIndex=0;
constexpr int coordsPerVertex=2;
gl.glVertexAttribPointer(attribIndex, coordsPerVertex, GL_FLOAT, false, 0, 0);
gl.glEnableVertexAttribArray(attribIndex);
gl.glBindVertexArray(0);

blitTexProgram.reset(new QOpenGLShaderProgram);
blitTexProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, 1+R"(
#version 330
layout(location=0) in vec4 vertex;
out vec2 texcoord;
void main()
{
gl_Position = vertex;
texcoord = vertex.st*0.5+vec2(0.5);
}
)");
blitTexProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, 1+R"(
#version 330
in vec2 texcoord;
out vec4 color;
uniform sampler2D tex;
void main()
{
color = texture(tex, texcoord);
}
)");
blitTexProgram->link();
blitTexProgram->bind();
blitTexProgram->setUniformValue("tex", 0);
blitTexProgram->release();

gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldFBO);
}

TextureAverageComputer::~TextureAverageComputer()
{
gl.glDeleteTextures(1, &potTex);
gl.glDeleteFramebuffers(1, &potFBO);
gl.glDeleteVertexArrays(1, &vao);
gl.glDeleteBuffers(1, &vbo);
}

#endif
54 changes: 54 additions & 0 deletions src/core/TextureAverageComputer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Stellarium
* Copyright (C) 2023 Ruslan Kabatsayev
*
* 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; either version 2
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*/

#ifndef INCLUDE_ONCE_D638566A_098E_4855_B0B5_C8BB5940DA10
#define INCLUDE_ONCE_D638566A_098E_4855_B0B5_C8BB5940DA10

#include <string>
#include <memory>
#include "VecMath.hpp"
#include <QOpenGLContext>
#include <QOpenGLShaderProgram>

#if !QT_CONFIG(opengles2) // This class uses glGetTexImage(), which is not supported in GLES2

class QOpenGLFunctions_3_3_Core;
class TextureAverageComputer
{
QOpenGLFunctions_3_3_Core& gl;
std::unique_ptr<QOpenGLShaderProgram> blitTexProgram;
GLuint potFBO = 0;
GLuint potTex = 0;
GLuint vbo = 0, vao = 0;
GLint npotWidth, npotHeight;
static inline bool inited = false;
static inline bool workaroundNeeded = false;

void init();
Vec4f getTextureAverageSimple(GLuint texture, int width, int height);
Vec4f getTextureAverageWithWorkaround(GLuint texture);
public:
Vec4f getTextureAverage(GLuint texture);
TextureAverageComputer(QOpenGLFunctions_3_3_Core&, int texW, int texH, GLenum internalFormat);
~TextureAverageComputer();
};

#endif

#endif
34 changes: 6 additions & 28 deletions src/core/modules/AtmosphereShowMySky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "StelPainter.hpp"
#include "Dithering.hpp"
#include "StelTranslator.hpp"
#include "TextureAverageComputer.hpp"

#include <cassert>
#include <cstring>
Expand Down Expand Up @@ -316,7 +317,10 @@ vec3 calcViewDir()

void AtmosphereShowMySky::resizeRenderTarget(int width, int height)
{
renderer_->resizeEvent(width/atmoRes, height/atmoRes);
const int physWidth = width/atmoRes;
const int physHeight = height/atmoRes;
renderer_->resizeEvent(physWidth, physHeight);
textureAverager_.reset(new TextureAverageComputer(*glfuncs(), physWidth, physHeight, GL_RGBA32F));

prevWidth_=width;
prevHeight_=height;
Expand Down Expand Up @@ -697,33 +701,7 @@ void AtmosphereShowMySky::drawAtmosphere(Mat4f const& projectionMatrix, const fl
Vec4f AtmosphereShowMySky::getMeanPixelValue()
{
StelOpenGL::checkGLErrors(__FILE__,__LINE__);
auto& gl = *glfuncs();

GL(gl.glActiveTexture(GL_TEXTURE0));
GL(gl.glBindTexture(GL_TEXTURE_2D, renderer_->getLuminanceTexture()));
GL(gl.glGenerateMipmap(GL_TEXTURE_2D));

int texW=-1, texH=-1;
GL(gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW));
GL(gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH));

using namespace std;
// Formula from the glspec, "Mipmapping" subsection in section 3.8.11 Texture Minification
const auto totalMipmapLevels = 1+floor(log2(max(texW,texH)));
const auto deepestLevel=totalMipmapLevels-1;

#ifndef NDEBUG
// Sanity check
int deepestMipmapLevelWidth=-1, deepestMipmapLevelHeight=-1;
GL(gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_WIDTH, &deepestMipmapLevelWidth));
GL(gl.glGetTexLevelParameteriv(GL_TEXTURE_2D, deepestLevel, GL_TEXTURE_HEIGHT, &deepestMipmapLevelHeight));
assert(deepestMipmapLevelWidth==1);
assert(deepestMipmapLevelHeight==1);
#endif

Vec4f pixel;
GL(gl.glGetTexImage(GL_TEXTURE_2D, deepestLevel, GL_RGBA, GL_FLOAT, &pixel[0]));
return pixel;
return textureAverager_->getTextureAverage(renderer_->getLuminanceTexture());
}

bool AtmosphereShowMySky::dynamicResolution(StelProjectorP prj, Vec3d &currPos, int width, int height)
Expand Down
2 changes: 2 additions & 0 deletions src/core/modules/AtmosphereShowMySky.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class StelProjector;
class StelToneReproducer;
class StelCore;
class QOpenGLFunctions;
class TextureAverageComputer;

class AtmosphereShowMySky : public Atmosphere, public QObject
{
Expand Down Expand Up @@ -115,6 +116,7 @@ class AtmosphereShowMySky : public Atmosphere, public QObject
} shaderAttribLocations;

StelTextureSP ditherPatternTex_;
std::unique_ptr<TextureAverageComputer> textureAverager_;

float prevFad=0, prevFov=0;
Vec3d prevPos=Vec3d(0,0,0), prevSun=Vec3d(0,0,0);
Expand Down