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
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ qt_add_qml_module(scratchcpp-render
internal/ValueMonitor.qml
internal/MonitorSlider.qml
internal/ListMonitor.qml
shaders/sprite.vert
shaders/sprite.frag
SOURCES
global.h
projectloader.cpp
Expand Down Expand Up @@ -56,6 +58,10 @@ qt_add_qml_module(scratchcpp-render
penlayerpainter.h
penattributes.h
penstate.h
shadermanager.cpp
shadermanager.h
graphicseffect.cpp
graphicseffect.h
blocks/penextension.cpp
blocks/penextension.h
blocks/penblocks.cpp
Expand Down
21 changes: 21 additions & 0 deletions src/graphicseffect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "graphicseffect.h"

using namespace scratchcpprender;

GraphicsEffect::GraphicsEffect(ShaderManager::Effect effect, const std::string &name) :
m_effect(effect),
m_name(name)
{
}

ShaderManager::Effect GraphicsEffect::effect() const
{
return m_effect;
}

std::string GraphicsEffect::name() const
{
return m_name;
}
25 changes: 25 additions & 0 deletions src/graphicseffect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <scratchcpp/igraphicseffect.h>

#include "shadermanager.h"

namespace scratchcpprender
{

class GraphicsEffect : public libscratchcpp::IGraphicsEffect
{
public:
GraphicsEffect(ShaderManager::Effect effect, const std::string &name);

ShaderManager::Effect effect() const;
std::string name() const override;

private:
ShaderManager::Effect m_effect = static_cast<ShaderManager::Effect>(0);
std::string m_name;
};

} // namespace scratchcpprender
6 changes: 6 additions & 0 deletions src/irenderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <qnanoquickitem.h>
#include <scratchcpp/sprite.h>

#include "shadermanager.h"

class QBuffer;
class QNanoPainter;
class QOpenGLContext;
Expand Down Expand Up @@ -75,6 +77,10 @@ class IRenderedTarget : public QNanoQuickItem

virtual Texture texture() const = 0;

virtual const std::unordered_map<ShaderManager::Effect, double> &graphicEffects() const = 0;
virtual void setGraphicEffect(ShaderManager::Effect effect, double value) = 0;
virtual void clearGraphicEffects() = 0;

virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
virtual const std::vector<QPointF> &hullPoints() const = 0;
};
Expand Down
36 changes: 36 additions & 0 deletions src/renderedtarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,42 @@ Texture RenderedTarget::texture() const
return m_texture;
}

const std::unordered_map<ShaderManager::Effect, double> &RenderedTarget::graphicEffects() const
{
return m_graphicEffects;
}

void RenderedTarget::setGraphicEffect(ShaderManager::Effect effect, double value)
{
bool changed = false;
auto it = m_graphicEffects.find(effect);

if (value == 0) {
if (it != m_graphicEffects.cend()) {
changed = true;
m_graphicEffects.erase(effect);
}
} else {
if (it != m_graphicEffects.cend())
changed = it->second != value;
else
changed = true;

m_graphicEffects[effect] = value;
}

if (changed)
update();
}

void RenderedTarget::clearGraphicEffects()
{
if (!m_graphicEffects.empty())
update();

m_graphicEffects.clear();
}

void RenderedTarget::updateHullPoints(QOpenGLFramebufferObject *fbo)
{
if (m_stageModel)
Expand Down
5 changes: 5 additions & 0 deletions src/renderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class RenderedTarget : public IRenderedTarget

Texture texture() const override;

const std::unordered_map<ShaderManager::Effect, double> &graphicEffects() const override;
void setGraphicEffect(ShaderManager::Effect effect, double value) override;
void clearGraphicEffects() override;

void updateHullPoints(QOpenGLFramebufferObject *fbo) override;
const std::vector<QPointF> &hullPoints() const override;

Expand Down Expand Up @@ -119,6 +123,7 @@ class RenderedTarget : public IRenderedTarget
Skin *m_skin = nullptr;
Texture m_texture;
Texture m_oldTexture;
std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
double m_size = 1;
double m_x = 0;
double m_y = 0;
Expand Down
162 changes: 162 additions & 0 deletions src/shadermanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include <QOpenGLShaderProgram>
#include <QOpenGLContext>
#include <QFile>
#include <scratchcpp/scratchconfiguration.h>

#include "shadermanager.h"
#include "graphicseffect.h"

using namespace scratchcpprender;

using ConverterFunc = float (*)(float);

static float wrapClamp(float n, float min, float max)
{
// TODO: Move this to a separate class
const float range = max - min;
return n - (std::floor((n - min) / range) * range);
}

static const QString VERTEX_SHADER_SRC = ":/qt/qml/ScratchCPP/Render/shaders/sprite.vert";
static const QString FRAGMENT_SHADER_SRC = ":/qt/qml/ScratchCPP/Render/shaders/sprite.frag";

static const char *TEXTURE_UNIT_UNIFORM = "u_skin";

static const std::unordered_map<ShaderManager::Effect, const char *>
EFFECT_TO_NAME = { { ShaderManager::Effect::Color, "color" }, { ShaderManager::Effect::Brightness, "brightness" }, { ShaderManager::Effect::Ghost, "ghost" } };

static const std::unordered_map<ShaderManager::Effect, const char *>
EFFECT_UNIFORM_NAME = { { ShaderManager::Effect::Color, "u_color" }, { ShaderManager::Effect::Brightness, "u_brightness" }, { ShaderManager::Effect::Ghost, "u_ghost" } };

static const std::unordered_map<ShaderManager::Effect, ConverterFunc> EFFECT_CONVERTER = {
{ ShaderManager::Effect::Color, [](float x) { return wrapClamp(x / 200.0f, 0.0f, 1.0f); } },
{ ShaderManager::Effect::Brightness, [](float x) { return std::clamp(x, -100.0f, 100.0f) / 100.0f; } },
{ ShaderManager::Effect::Ghost, [](float x) { return 1 - std::clamp(x, 0.0f, 100.0f) / 100.0f; } }
};

static const std::unordered_map<ShaderManager::Effect, bool>
EFFECT_SHAPE_CHANGES = { { ShaderManager::Effect::Color, false }, { ShaderManager::Effect::Brightness, false }, { ShaderManager::Effect::Ghost, false } };

Q_GLOBAL_STATIC(ShaderManager, globalInstance)

ShaderManager::Registrar ShaderManager::m_registrar;

ShaderManager::ShaderManager(QObject *parent) :
QObject(parent)
{
QOpenGLContext *context = QOpenGLContext::currentContext();
Q_ASSERT(context);

if (!context) {
qWarning("ShaderManager must be constructed with a valid OpenGL context.");
return;
}

// Compile the vertex shader (it will be used in any shader program)
QByteArray vertexShaderSource;
QFile vertSource(VERTEX_SHADER_SRC);
vertSource.open(QFile::ReadOnly);
vertexShaderSource = "#version 330 core\n" + vertSource.readAll();

m_vertexShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
m_vertexShader->compileSourceCode(vertexShaderSource);
Q_ASSERT(m_vertexShader->isCompiled());

// Load the fragment shader source code
QFile fragSource(FRAGMENT_SHADER_SRC);
fragSource.open(QFile::ReadOnly);
m_fragmentShaderSource = fragSource.readAll();
Q_ASSERT(!m_fragmentShaderSource.isEmpty());
}

ShaderManager *ShaderManager::instance()
{
return globalInstance;
}

QOpenGLShaderProgram *ShaderManager::getShaderProgram(const std::unordered_map<Effect, double> &effectValues)
{
int effectBits = 0;
bool firstSet = false;

for (const auto &[effect, value] : effectValues) {
if (value != 0)
effectBits |= static_cast<int>(effect);
}

// Find the selected effect combination
auto it = m_shaderPrograms.find(effectBits);

if (it == m_shaderPrograms.cend()) {
// Create a new shader program if this combination doesn't exist yet
QOpenGLShaderProgram *program = createShaderProgram(effectValues);

if (program)
m_shaderPrograms[effectBits] = program;

return program;
} else
return it->second;
}

void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues)
{
// Set the texture unit
program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit);

// Set the uniform values for the enabled effects and reset the other effects
for (const auto &[effect, name] : EFFECT_TO_NAME) {
const auto it = effectValues.find(effect);
double value;

if (it == effectValues.cend())
value = 0; // reset the effect
else
value = it->second;

auto converter = EFFECT_CONVERTER.at(effect);
program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), converter(value));
}
}

void ShaderManager::registerEffects()
{
// Register graphic effects in libscratchcpp
for (const auto &[effect, name] : EFFECT_TO_NAME) {
auto effectObj = std::make_shared<GraphicsEffect>(effect, name);
libscratchcpp::ScratchConfiguration::registerGraphicsEffect(effectObj);
}
}

QOpenGLShaderProgram *ShaderManager::createShaderProgram(const std::unordered_map<Effect, double> &effectValues)
{
QOpenGLContext *context = QOpenGLContext::currentContext();
Q_ASSERT(context && m_vertexShader);

if (!context || !m_vertexShader)
return nullptr;

// Version must be defined in the first line
QByteArray fragSource = "#version 330\n";

// Add defines for the effects
for (const auto &[effect, value] : effectValues) {
if (value != 0) {
fragSource.push_back("#define ENABLE_");
fragSource.push_back(EFFECT_TO_NAME.at(effect));
fragSource.push_back('\n');
}
}

// Add the actual fragment shader
fragSource.push_back(m_fragmentShaderSource);

QOpenGLShaderProgram *program = new QOpenGLShaderProgram(this);
program->addShader(m_vertexShader);
program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource);
program->link();

return program;
}
48 changes: 48 additions & 0 deletions src/shadermanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QObject>
#include <memory>

class QOpenGLShaderProgram;
class QOpenGLShader;

namespace scratchcpprender
{

class ShaderManager : public QObject
{
public:
enum class Effect
{
Color = 1 << 0,
Brightness = 1 << 1,
Ghost = 1 << 2
};

explicit ShaderManager(QObject *parent = nullptr);

static ShaderManager *instance();

QOpenGLShaderProgram *getShaderProgram(const std::unordered_map<Effect, double> &effectValues);
void setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues);

private:
struct Registrar
{
Registrar() { registerEffects(); }
};

static void registerEffects();

QOpenGLShaderProgram *createShaderProgram(const std::unordered_map<Effect, double> &effectValues);

static Registrar m_registrar;

QOpenGLShader *m_vertexShader = nullptr;
std::unordered_map<int, QOpenGLShaderProgram *> m_shaderPrograms;
QByteArray m_fragmentShaderSource;
};

} // namespace scratchcpprender
Loading