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 @@ -49,6 +49,12 @@ qt_add_qml_module(scratchcpp-render
mouseeventhandler.h
keyeventhandler.cpp
keyeventhandler.h
ipenlayer.h
penlayer.cpp
penlayer.h
penlayerpainter.cpp
penlayerpainter.h
penattributes.h
)

list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
Expand Down
7 changes: 7 additions & 0 deletions src/ProjectPlayer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ ProjectScene {
onStageModelChanged: stageModel.renderedTarget = this
}

PenLayer {
id: projectPenLayer
engine: loader.engine
anchors.fill: parent
}

Component {
id: renderedSprite

Expand All @@ -121,6 +127,7 @@ ProjectScene {
engine = loader.engine;
spriteModel = modelData;
spriteModel.renderedTarget = this;
spriteModel.penLayer = projectPenLayer;
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions src/ipenlayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <qnanoquickitem.h>

namespace libscratchcpp
{

class IEngine;

}

namespace scratchcpprender
{

struct PenAttributes;

class IPenLayer : public QNanoQuickItem
{
public:
IPenLayer(QNanoQuickItem *parent = nullptr) :
QNanoQuickItem(parent)
{
}

virtual ~IPenLayer() { }

virtual bool antialiasingEnabled() const = 0;
virtual void setAntialiasingEnabled(bool enabled) = 0;

virtual libscratchcpp::IEngine *engine() const = 0;
virtual void setEngine(libscratchcpp::IEngine *newEngine) = 0;

virtual void clear() = 0;
virtual void drawPoint(const PenAttributes &penAttributes, double x, double y) = 0;
virtual void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) = 0;

virtual QOpenGLFramebufferObject *framebufferObject() const = 0;
};

} // namespace scratchcpprender
16 changes: 16 additions & 0 deletions src/penattributes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QColor>

namespace scratchcpprender
{

struct PenAttributes
{
QColor color = QColor(0, 0, 255);
double diameter = 1;
};

} // namespace scratchcpprender
138 changes: 138 additions & 0 deletions src/penlayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "penlayer.h"
#include "penlayerpainter.h"
#include "penattributes.h"

using namespace scratchcpprender;

std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;

PenLayer::PenLayer(QNanoQuickItem *parent) :
IPenLayer(parent)
{
m_fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
m_fboFormat.setSamples(m_antialiasingEnabled ? 4 : 0);
}

PenLayer::~PenLayer()
{
if (m_engine)
m_projectPenLayers.erase(m_engine);
}

bool PenLayer::antialiasingEnabled() const
{
return m_antialiasingEnabled;
}

void PenLayer::setAntialiasingEnabled(bool enabled)
{
m_antialiasingEnabled = enabled;
m_fboFormat.setSamples(enabled ? 4 : 0);
}

libscratchcpp::IEngine *PenLayer::engine() const
{
return m_engine;
}

void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
{
if (m_engine == newEngine)
return;

if (m_engine)
m_projectPenLayers.erase(m_engine);

m_engine = newEngine;

if (m_engine) {
m_projectPenLayers[m_engine] = this;
m_fbo = std::make_unique<QOpenGLFramebufferObject>(m_engine->stageWidth(), m_engine->stageHeight(), m_fboFormat);
Q_ASSERT(m_fbo->isValid());

m_paintDevice = std::make_unique<QOpenGLPaintDevice>(m_fbo->size());
clear();
}

emit engineChanged();
}

void scratchcpprender::PenLayer::clear()
{
if (!m_fbo)
return;

m_fbo->bind();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
m_fbo->release();

update();
}

void scratchcpprender::PenLayer::drawPoint(const PenAttributes &penAttributes, double x, double y)
{
drawLine(penAttributes, x, y, x, y);
}

void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1)
{
if (!m_fbo || !m_paintDevice || !m_engine)
return;

// Begin painting
m_fbo->bind();
QPainter painter(m_paintDevice.get());
painter.beginNativePainting();
painter.setRenderHint(QPainter::Antialiasing, m_antialiasingEnabled);
painter.setRenderHint(QPainter::SmoothPixmapTransform, false);

// Translate to Scratch coordinate system
double stageWidthHalf = m_engine->stageWidth() / 2;
double stageHeightHalf = m_engine->stageHeight() / 2;
x0 += stageWidthHalf;
y0 = stageHeightHalf - y0;
x1 += stageWidthHalf;
y1 = stageHeightHalf - y1;

// Set pen attributes
QPen pen(penAttributes.color);
pen.setWidthF(penAttributes.diameter);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);

// If the start and end coordinates are the same, draw a point, otherwise draw a line
if (x0 == x1 && y0 == y1)
painter.drawPoint(x0, y0);
else
painter.drawLine(x0, y0, x1, y1);

// End painting
painter.endNativePainting();
painter.end();
m_fbo->release();

update();
}

QOpenGLFramebufferObject *PenLayer::framebufferObject() const
{
return m_fbo.get();
}

IPenLayer *PenLayer::getProjectPenLayer(libscratchcpp::IEngine *engine)
{
auto it = m_projectPenLayers.find(engine);

if (it != m_projectPenLayers.cend())
return it->second;

return nullptr;
}

QNanoQuickItemPainter *PenLayer::createItemPainter() const
{
return new PenLayerPainter;
}
53 changes: 53 additions & 0 deletions src/penlayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <ipenlayer.h>
#include <QOpenGLFramebufferObject>
#include <QOpenGLPaintDevice>
#include <QPainter>
#include <scratchcpp/iengine.h>

namespace scratchcpprender
{

class PenLayer : public IPenLayer
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged)

public:
PenLayer(QNanoQuickItem *parent = nullptr);
~PenLayer();

bool antialiasingEnabled() const override;
void setAntialiasingEnabled(bool enabled) override;

libscratchcpp::IEngine *engine() const override;
void setEngine(libscratchcpp::IEngine *newEngine) override;

void clear() override;
void drawPoint(const PenAttributes &penAttributes, double x, double y) override;
void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override;

QOpenGLFramebufferObject *framebufferObject() const override;

static IPenLayer *getProjectPenLayer(libscratchcpp::IEngine *engine);

signals:
void engineChanged();

protected:
QNanoQuickItemPainter *createItemPainter() const override;

private:
static std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> m_projectPenLayers;
bool m_antialiasingEnabled = true;
libscratchcpp::IEngine *m_engine = nullptr;
std::unique_ptr<QOpenGLFramebufferObject> m_fbo;
std::unique_ptr<QOpenGLPaintDevice> m_paintDevice;
QOpenGLFramebufferObjectFormat m_fboFormat;
};

} // namespace scratchcpprender
45 changes: 45 additions & 0 deletions src/penlayerpainter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "penlayerpainter.h"
#include "penlayer.h"

using namespace scratchcpprender;

PenLayerPainter::PenLayerPainter(QOpenGLFramebufferObject *fbo)
{
m_targetFbo = fbo;
}

void PenLayerPainter::paint(QNanoPainter *painter)
{
if (QThread::currentThread() != qApp->thread()) {
qFatal("Error: Rendering must happen in the GUI thread to work correctly. Please disable threaded render loop using qputenv(\"QSG_RENDER_LOOP\", \"basic\") before constructing your "
"application object.");
}

QOpenGLContext *context = QOpenGLContext::currentContext();
Q_ASSERT(context);

if (!context || !m_fbo)
return;

// Custom FBO - only used for testing
QOpenGLFramebufferObject *targetFbo = m_targetFbo ? m_targetFbo : framebufferObject();

QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);

// Blit the FBO to a temporary FBO first (multisampled FBOs can only be blitted to FBOs with the same size)
QOpenGLFramebufferObject tmpFbo(m_fbo->size(), format);
QOpenGLFramebufferObject::blitFramebuffer(&tmpFbo, m_fbo);
QOpenGLFramebufferObject::blitFramebuffer(targetFbo, &tmpFbo);
}

void PenLayerPainter::synchronize(QNanoQuickItem *item)
{
IPenLayer *penLayer = dynamic_cast<IPenLayer *>(item);
Q_ASSERT(penLayer);

if (penLayer)
m_fbo = penLayer->framebufferObject();
}
25 changes: 25 additions & 0 deletions src/penlayerpainter.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 <qnanoquickitempainter.h>

#include "texture.h"

namespace scratchcpprender
{

class PenLayerPainter : public QNanoQuickItemPainter
{
public:
PenLayerPainter(QOpenGLFramebufferObject *fbo = nullptr);

void paint(QNanoPainter *painter) override;
void synchronize(QNanoQuickItem *item) override;

private:
QOpenGLFramebufferObject *m_targetFbo = nullptr;
QOpenGLFramebufferObject *m_fbo = nullptr;
};

} // namespace scratchcpprender
Loading