Skip to content

Commit f5d4508

Browse files
committed
Add ShaderManager class
1 parent 51f225b commit f5d4508

File tree

6 files changed

+530
-0
lines changed

6 files changed

+530
-0
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ qt_add_qml_module(scratchcpp-render
5858
penlayerpainter.h
5959
penattributes.h
6060
penstate.h
61+
shadermanager.cpp
62+
shadermanager.h
6163
graphicseffect.cpp
6264
graphicseffect.h
6365
blocks/penextension.cpp

src/shadermanager.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include <QOpenGLShaderProgram>
4+
#include <QOpenGLContext>
5+
#include <QFile>
6+
#include <scratchcpp/scratchconfiguration.h>
7+
8+
#include "shadermanager.h"
9+
#include "graphicseffect.h"
10+
11+
using namespace scratchcpprender;
12+
13+
using ConverterFunc = float (*)(float);
14+
15+
static float wrapClamp(float n, float min, float max)
16+
{
17+
// TODO: Move this to a separate class
18+
const float range = max - min;
19+
return n - (std::floor((n - min) / range) * range);
20+
}
21+
22+
static const QString VERTEX_SHADER_SRC = ":/qt/qml/ScratchCPP/Render/shaders/sprite.vert";
23+
static const QString FRAGMENT_SHADER_SRC = ":/qt/qml/ScratchCPP/Render/shaders/sprite.frag";
24+
25+
static const char *TEXTURE_UNIT_UNIFORM = "u_skin";
26+
27+
static const std::unordered_map<ShaderManager::Effect, const char *>
28+
EFFECT_TO_NAME = { { ShaderManager::Effect::Color, "color" }, { ShaderManager::Effect::Brightness, "brightness" }, { ShaderManager::Effect::Ghost, "ghost" } };
29+
30+
static const std::unordered_map<ShaderManager::Effect, const char *>
31+
EFFECT_UNIFORM_NAME = { { ShaderManager::Effect::Color, "u_color" }, { ShaderManager::Effect::Brightness, "u_brightness" }, { ShaderManager::Effect::Ghost, "u_ghost" } };
32+
33+
static const std::unordered_map<ShaderManager::Effect, ConverterFunc> EFFECT_CONVERTER = {
34+
{ ShaderManager::Effect::Color, [](float x) { return wrapClamp(x / 200.0f, 0.0f, 1.0f); } },
35+
{ ShaderManager::Effect::Brightness, [](float x) { return std::clamp(x, -100.0f, 100.0f) / 100.0f; } },
36+
{ ShaderManager::Effect::Ghost, [](float x) { return 1 - std::clamp(x, 0.0f, 100.0f) / 100.0f; } }
37+
};
38+
39+
static const std::unordered_map<ShaderManager::Effect, bool>
40+
EFFECT_SHAPE_CHANGES = { { ShaderManager::Effect::Color, false }, { ShaderManager::Effect::Brightness, false }, { ShaderManager::Effect::Ghost, false } };
41+
42+
Q_GLOBAL_STATIC(ShaderManager, globalInstance)
43+
44+
ShaderManager::Registrar ShaderManager::m_registrar;
45+
46+
ShaderManager::ShaderManager(QObject *parent) :
47+
QObject(parent)
48+
{
49+
QOpenGLContext *context = QOpenGLContext::currentContext();
50+
Q_ASSERT(context);
51+
52+
if (!context) {
53+
qWarning("ShaderManager must be constructed with a valid OpenGL context.");
54+
return;
55+
}
56+
57+
// Compile the vertex shader (it will be used in any shader program)
58+
QByteArray vertexShaderSource;
59+
QFile vertSource(VERTEX_SHADER_SRC);
60+
vertSource.open(QFile::ReadOnly);
61+
vertexShaderSource = "#version 330 core\n" + vertSource.readAll();
62+
63+
m_vertexShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
64+
m_vertexShader->compileSourceCode(vertexShaderSource);
65+
Q_ASSERT(m_vertexShader->isCompiled());
66+
67+
// Load the fragment shader source code
68+
QFile fragSource(FRAGMENT_SHADER_SRC);
69+
fragSource.open(QFile::ReadOnly);
70+
m_fragmentShaderSource = fragSource.readAll();
71+
Q_ASSERT(!m_fragmentShaderSource.isEmpty());
72+
}
73+
74+
ShaderManager *ShaderManager::instance()
75+
{
76+
return globalInstance;
77+
}
78+
79+
QOpenGLShaderProgram *ShaderManager::getShaderProgram(const std::unordered_map<Effect, double> &effectValues)
80+
{
81+
int effectBits = 0;
82+
bool firstSet = false;
83+
84+
for (const auto &[effect, value] : effectValues) {
85+
if (value != 0)
86+
effectBits |= static_cast<int>(effect);
87+
}
88+
89+
// Find the selected effect combination
90+
auto it = m_shaderPrograms.find(effectBits);
91+
92+
if (it == m_shaderPrograms.cend()) {
93+
// Create a new shader program if this combination doesn't exist yet
94+
QOpenGLShaderProgram *program = createShaderProgram(effectValues);
95+
96+
if (program)
97+
m_shaderPrograms[effectBits] = program;
98+
99+
return program;
100+
} else
101+
return it->second;
102+
}
103+
104+
void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues)
105+
{
106+
// Set the texture unit
107+
program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit);
108+
109+
// Set the uniform values for the enabled effects and reset the other effects
110+
for (const auto &[effect, name] : EFFECT_TO_NAME) {
111+
const auto it = effectValues.find(effect);
112+
double value;
113+
114+
if (it == effectValues.cend())
115+
value = 0; // reset the effect
116+
else
117+
value = it->second;
118+
119+
auto converter = EFFECT_CONVERTER.at(effect);
120+
program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), converter(value));
121+
}
122+
}
123+
124+
void ShaderManager::registerEffects()
125+
{
126+
// Register graphic effects in libscratchcpp
127+
for (const auto &[effect, name] : EFFECT_TO_NAME) {
128+
auto effectObj = std::make_shared<GraphicsEffect>(effect, name);
129+
libscratchcpp::ScratchConfiguration::registerGraphicsEffect(effectObj);
130+
}
131+
}
132+
133+
QOpenGLShaderProgram *ShaderManager::createShaderProgram(const std::unordered_map<Effect, double> &effectValues)
134+
{
135+
QOpenGLContext *context = QOpenGLContext::currentContext();
136+
Q_ASSERT(context && m_vertexShader);
137+
138+
if (!context || !m_vertexShader)
139+
return nullptr;
140+
141+
// Version must be defined in the first line
142+
QByteArray fragSource = "#version 330\n";
143+
144+
// Add defines for the effects
145+
for (const auto &[effect, value] : effectValues) {
146+
if (value != 0) {
147+
fragSource.push_back("#define ENABLE_");
148+
fragSource.push_back(EFFECT_TO_NAME.at(effect));
149+
fragSource.push_back('\n');
150+
}
151+
}
152+
153+
// Add the actual fragment shader
154+
fragSource.push_back(m_fragmentShaderSource);
155+
156+
QOpenGLShaderProgram *program = new QOpenGLShaderProgram(this);
157+
program->addShader(m_vertexShader);
158+
program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource);
159+
program->link();
160+
161+
return program;
162+
}

src/shadermanager.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <QObject>
6+
#include <memory>
7+
8+
class QOpenGLShaderProgram;
9+
class QOpenGLShader;
10+
11+
namespace scratchcpprender
12+
{
13+
14+
class ShaderManager : public QObject
15+
{
16+
public:
17+
enum class Effect
18+
{
19+
Color = 1 << 0,
20+
Brightness = 1 << 1,
21+
Ghost = 1 << 2
22+
};
23+
24+
explicit ShaderManager(QObject *parent = nullptr);
25+
26+
static ShaderManager *instance();
27+
28+
QOpenGLShaderProgram *getShaderProgram(const std::unordered_map<Effect, double> &effectValues);
29+
void setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues);
30+
31+
private:
32+
struct Registrar
33+
{
34+
Registrar() { registerEffects(); }
35+
};
36+
37+
static void registerEffects();
38+
39+
QOpenGLShaderProgram *createShaderProgram(const std::unordered_map<Effect, double> &effectValues);
40+
41+
static Registrar m_registrar;
42+
43+
QOpenGLShader *m_vertexShader = nullptr;
44+
std::unordered_map<int, QOpenGLShaderProgram *> m_shaderPrograms;
45+
QByteArray m_fragmentShaderSource;
46+
};
47+
48+
} // namespace scratchcpprender

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ add_subdirectory(penlayer)
3737
add_subdirectory(penlayerpainter)
3838
add_subdirectory(blocks)
3939
add_subdirectory(graphicseffect)
40+
add_subdirectory(shadermanager)

test/shadermanager/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
add_executable(
2+
shadermanager_test
3+
shadermanager_test.cpp
4+
)
5+
6+
target_link_libraries(
7+
shadermanager_test
8+
GTest::gtest_main
9+
scratchcpp-render
10+
qnanopainter
11+
)
12+
13+
add_test(shadermanager_test)
14+
gtest_discover_tests(shadermanager_test)

0 commit comments

Comments
 (0)