Skip to content

Commit 27a2cd3

Browse files
authored
Merge pull request #83 from scratchcpp/color_effects
Implement color effects
2 parents 4a558a3 + 48bfcf7 commit 27a2cd3

25 files changed

+985
-25
lines changed

src/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ qt_add_qml_module(scratchcpp-render
1212
internal/ValueMonitor.qml
1313
internal/MonitorSlider.qml
1414
internal/ListMonitor.qml
15+
shaders/sprite.vert
16+
shaders/sprite.frag
1517
SOURCES
1618
global.h
1719
projectloader.cpp
@@ -56,6 +58,10 @@ qt_add_qml_module(scratchcpp-render
5658
penlayerpainter.h
5759
penattributes.h
5860
penstate.h
61+
shadermanager.cpp
62+
shadermanager.h
63+
graphicseffect.cpp
64+
graphicseffect.h
5965
blocks/penextension.cpp
6066
blocks/penextension.h
6167
blocks/penblocks.cpp

src/graphicseffect.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include "graphicseffect.h"
4+
5+
using namespace scratchcpprender;
6+
7+
GraphicsEffect::GraphicsEffect(ShaderManager::Effect effect, const std::string &name) :
8+
m_effect(effect),
9+
m_name(name)
10+
{
11+
}
12+
13+
ShaderManager::Effect GraphicsEffect::effect() const
14+
{
15+
return m_effect;
16+
}
17+
18+
std::string GraphicsEffect::name() const
19+
{
20+
return m_name;
21+
}

src/graphicseffect.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <scratchcpp/igraphicseffect.h>
6+
7+
#include "shadermanager.h"
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class GraphicsEffect : public libscratchcpp::IGraphicsEffect
13+
{
14+
public:
15+
GraphicsEffect(ShaderManager::Effect effect, const std::string &name);
16+
17+
ShaderManager::Effect effect() const;
18+
std::string name() const override;
19+
20+
private:
21+
ShaderManager::Effect m_effect = static_cast<ShaderManager::Effect>(0);
22+
std::string m_name;
23+
};
24+
25+
} // namespace scratchcpprender

src/irenderedtarget.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <qnanoquickitem.h>
77
#include <scratchcpp/sprite.h>
88

9+
#include "shadermanager.h"
10+
911
class QBuffer;
1012
class QNanoPainter;
1113
class QOpenGLContext;
@@ -75,6 +77,10 @@ class IRenderedTarget : public QNanoQuickItem
7577

7678
virtual Texture texture() const = 0;
7779

80+
virtual const std::unordered_map<ShaderManager::Effect, double> &graphicEffects() const = 0;
81+
virtual void setGraphicEffect(ShaderManager::Effect effect, double value) = 0;
82+
virtual void clearGraphicEffects() = 0;
83+
7884
virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
7985
virtual const std::vector<QPointF> &hullPoints() const = 0;
8086
};

src/renderedtarget.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,42 @@ Texture RenderedTarget::texture() const
425425
return m_texture;
426426
}
427427

428+
const std::unordered_map<ShaderManager::Effect, double> &RenderedTarget::graphicEffects() const
429+
{
430+
return m_graphicEffects;
431+
}
432+
433+
void RenderedTarget::setGraphicEffect(ShaderManager::Effect effect, double value)
434+
{
435+
bool changed = false;
436+
auto it = m_graphicEffects.find(effect);
437+
438+
if (value == 0) {
439+
if (it != m_graphicEffects.cend()) {
440+
changed = true;
441+
m_graphicEffects.erase(effect);
442+
}
443+
} else {
444+
if (it != m_graphicEffects.cend())
445+
changed = it->second != value;
446+
else
447+
changed = true;
448+
449+
m_graphicEffects[effect] = value;
450+
}
451+
452+
if (changed)
453+
update();
454+
}
455+
456+
void RenderedTarget::clearGraphicEffects()
457+
{
458+
if (!m_graphicEffects.empty())
459+
update();
460+
461+
m_graphicEffects.clear();
462+
}
463+
428464
void RenderedTarget::updateHullPoints(QOpenGLFramebufferObject *fbo)
429465
{
430466
if (m_stageModel)

src/renderedtarget.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ class RenderedTarget : public IRenderedTarget
8282

8383
Texture texture() const override;
8484

85+
const std::unordered_map<ShaderManager::Effect, double> &graphicEffects() const override;
86+
void setGraphicEffect(ShaderManager::Effect effect, double value) override;
87+
void clearGraphicEffects() override;
88+
8589
void updateHullPoints(QOpenGLFramebufferObject *fbo) override;
8690
const std::vector<QPointF> &hullPoints() const override;
8791

@@ -119,6 +123,7 @@ class RenderedTarget : public IRenderedTarget
119123
Skin *m_skin = nullptr;
120124
Texture m_texture;
121125
Texture m_oldTexture;
126+
std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
122127
double m_size = 1;
123128
double m_x = 0;
124129
double m_y = 0;

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

0 commit comments

Comments
 (0)