diff --git a/CMakeLists.txt b/CMakeLists.txt index 64781f7..aa1bca5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(scratchcpp-render VERSION 0.9.0 LANGUAGES CXX) +project(scratchcpp-render VERSION 0.9.2 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) diff --git a/libscratchcpp b/libscratchcpp index 8ed5355..6741f03 160000 --- a/libscratchcpp +++ b/libscratchcpp @@ -1 +1 @@ -Subproject commit 8ed5355643f3b92cd672cda7673dba1b4105bbb1 +Subproject commit 6741f0375700f531e2a23e4a1277756d897b0812 diff --git a/src/ProjectPlayer.qml b/src/ProjectPlayer.qml index 87f8f7e..b458ed4 100644 --- a/src/ProjectPlayer.qml +++ b/src/ProjectPlayer.qml @@ -192,6 +192,16 @@ ProjectScene { scale: hqPen ? 1 : stageScale transformOrigin: Item.TopLeft visible: !priv.loading + + onWidthChanged: { + if (!hqPen) + refresh(); + } + + onHeightChanged: { + if (!hqPen) + refresh(); + } } Component { @@ -263,22 +273,25 @@ ProjectScene { } } - Repeater { - id: sprites - model: loader.sprites - delegate: renderedSprite - } + Item { + // Sprites are wrapped in Item to ensure monitors appear above them + Repeater { + id: sprites + model: loader.sprites + delegate: renderedSprite + } - Repeater { - id: clones - model: ListModel {} - delegate: renderedSprite - } + Repeater { + id: clones + model: ListModel {} + delegate: renderedSprite + } - Repeater { - id: textBubbles - model: loader.sprites - delegate: renderedTextBubble + Repeater { + id: textBubbles + model: loader.sprites + delegate: renderedTextBubble + } } SceneMouseArea { diff --git a/src/penlayer.cpp b/src/penlayer.cpp index 7333517..b93cb30 100644 --- a/src/penlayer.cpp +++ b/src/penlayer.cpp @@ -68,9 +68,15 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) m_engine = newEngine; - if (m_engine && QOpenGLContext::currentContext()) { + if (!m_glCtx) { + m_glCtx = QOpenGLContext::currentContext(); + + if (m_glCtx) + m_surface = m_glCtx->surface(); + } + + if (m_engine && m_glCtx) { m_projectPenLayers[m_engine] = this; - createFbo(); if (!m_painter) m_painter = std::make_unique(); @@ -80,6 +86,8 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine) m_glF->initializeOpenGLFunctions(); } + refresh(); + if (m_vao == 0) { // Set up VBO and VAO float vertices[] = { -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f }; @@ -124,8 +132,8 @@ void PenLayer::setHqPen(bool newHqPen) return; m_hqPen = newHqPen; - createFbo(); emit hqPenChanged(); + refresh(); } void scratchcpprender::PenLayer::clear() @@ -324,6 +332,41 @@ void PenLayer::stamp(IRenderedTarget *target) update(); } +void PenLayer::refresh() +{ + if (!m_glCtx || !m_surface || !m_engine || !m_glF) + return; + + QOpenGLContext *oldCtx = QOpenGLContext::currentContext(); + QSurface *oldSurface = oldCtx->surface(); + + if (oldCtx != m_glCtx) { + oldCtx->doneCurrent(); + m_glCtx->makeCurrent(m_surface); + } + + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + + QOpenGLFramebufferObject *newFbo = new QOpenGLFramebufferObject(width(), height(), fboFormat); + Q_ASSERT(newFbo->isValid()); + + if (m_fbo) { + m_glF->glDisable(GL_SCISSOR_TEST); + QOpenGLFramebufferObject::blitFramebuffer(newFbo, m_fbo.get()); + m_glF->glEnable(GL_SCISSOR_TEST); + } + + m_fbo.reset(newFbo); + m_texture = Texture(m_fbo->texture(), m_fbo->size()); + m_scale = width() / m_engine->stageWidth(); + + if (oldCtx != m_glCtx) { + m_glCtx->doneCurrent(); + oldCtx->makeCurrent(oldSurface); + } +} + QOpenGLFramebufferObject *PenLayer::framebufferObject() const { return m_fbo.get(); @@ -427,36 +470,21 @@ void PenLayer::addPenLayer(libscratchcpp::IEngine *engine, IPenLayer *penLayer) QNanoQuickItemPainter *PenLayer::createItemPainter() const { + m_glCtx = QOpenGLContext::currentContext(); + Q_ASSERT(m_glCtx); + m_surface = m_glCtx->surface(); + Q_ASSERT(m_surface); return new PenLayerPainter; } void PenLayer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { if (m_hqPen && newGeometry != oldGeometry) - createFbo(); + refresh(); QNanoQuickItem::geometryChange(newGeometry, oldGeometry); } -void PenLayer::createFbo() -{ - if (!QOpenGLContext::currentContext() || !m_engine) - return; - - QOpenGLFramebufferObjectFormat fboFormat; - fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - - QOpenGLFramebufferObject *newFbo = new QOpenGLFramebufferObject(width(), height(), fboFormat); - Q_ASSERT(newFbo->isValid()); - - if (m_fbo) - QOpenGLFramebufferObject::blitFramebuffer(newFbo, m_fbo.get()); - - m_fbo.reset(newFbo); - m_texture = Texture(m_fbo->texture(), m_fbo->size()); - m_scale = width() / m_engine->stageWidth(); -} - void PenLayer::updateTexture() { if (!m_fbo) diff --git a/src/penlayer.h b/src/penlayer.h index d17eadc..53c614c 100644 --- a/src/penlayer.h +++ b/src/penlayer.h @@ -39,6 +39,8 @@ class PenLayer : public IPenLayer void drawLine(const PenAttributes &penAttributes, double x0, double y0, double x1, double y1) override; void stamp(IRenderedTarget *target) override; + Q_INVOKABLE void refresh(); + QOpenGLFramebufferObject *framebufferObject() const override; QRgb colorAtScratchPoint(double x, double y) const override; @@ -56,7 +58,6 @@ class PenLayer : public IPenLayer void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; private: - void createFbo(); void updateTexture(); static std::unordered_map m_projectPenLayers; @@ -65,6 +66,8 @@ class PenLayer : public IPenLayer libscratchcpp::IEngine *m_engine = nullptr; bool m_hqPen = false; std::unique_ptr m_fbo; + mutable QOpenGLContext *m_glCtx = nullptr; + mutable QSurface *m_surface = nullptr; double m_scale = 1; std::unique_ptr m_painter; std::unique_ptr m_glF; diff --git a/src/projectloader.cpp b/src/projectloader.cpp index 3126a5f..729ecf7 100644 --- a/src/projectloader.cpp +++ b/src/projectloader.cpp @@ -210,6 +210,24 @@ void ProjectLoader::timerEvent(QTimerEvent *event) return; if (m_engine) { + QOpenGLContext *oldCtx = QOpenGLContext::currentContext(); + QSurface *oldSurface = nullptr; + + if (!m_glCtx) + m_glCtx = oldCtx; + + if (m_glCtx) { + if (!m_surface) + m_surface = m_glCtx->surface(); + + oldSurface = oldCtx->surface(); + + if (oldCtx != m_glCtx) { + oldCtx->doneCurrent(); + m_glCtx->makeCurrent(m_surface); + } + } + for (Monitor *monitor : m_unpositionedMonitors) monitor->autoPosition(m_engine->monitors()); @@ -230,6 +248,13 @@ void ProjectLoader::timerEvent(QTimerEvent *event) m_renderTimer.restart(); } else m_renderFpsCounter++; + + if (m_glCtx) { + if (oldCtx != m_glCtx) { + m_glCtx->doneCurrent(); + oldCtx->makeCurrent(oldSurface); + } + } } event->accept(); diff --git a/src/projectloader.h b/src/projectloader.h index 8ada11c..249be57 100644 --- a/src/projectloader.h +++ b/src/projectloader.h @@ -12,6 +12,9 @@ Q_MOC_INCLUDE("spritemodel.h"); Q_MOC_INCLUDE("monitormodel.h"); +class QSurface; +class QOpenGLContext; + namespace scratchcpprender { @@ -185,6 +188,8 @@ class ProjectLoader : public QObject std::atomic m_downloadedAssets = 0; std::atomic m_assetCount = 0; std::atomic m_stopLoading = false; + QOpenGLContext *m_glCtx = nullptr; + QSurface *m_surface = nullptr; }; } // namespace scratchcpprender diff --git a/test/lines.png b/test/lines.png index 245885e..5792818 100644 Binary files a/test/lines.png and b/test/lines.png differ diff --git a/test/lines_hq.png b/test/lines_hq.png index c78d1d3..2cdc7e7 100644 Binary files a/test/lines_hq.png and b/test/lines_hq.png differ diff --git a/test/penlayer/penlayer_test.cpp b/test/penlayer/penlayer_test.cpp index 0142035..4ae36e1 100644 --- a/test/penlayer/penlayer_test.cpp +++ b/test/penlayer/penlayer_test.cpp @@ -148,6 +148,27 @@ TEST_F(PenLayerTest, FramebufferObject) ASSERT_EQ(fbo->format().samples(), 0); } +TEST_F(PenLayerTest, Refresh) +{ + PenLayer penLayer; + + EngineMock engine1, engine2, engine3; + penLayer.setWidth(480); + penLayer.setHeight(360); + EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(480)); + penLayer.setEngine(&engine1); + + penLayer.setWidth(500); + penLayer.setHeight(400); + + EXPECT_CALL(engine1, stageWidth()).WillOnce(Return(500)); + penLayer.refresh(); + + QOpenGLFramebufferObject *fbo = penLayer.framebufferObject(); + ASSERT_EQ(fbo->width(), 500); + ASSERT_EQ(fbo->height(), 400); +} + TEST_F(PenLayerTest, GetProjectPenLayer) { PenLayer penLayer; @@ -315,9 +336,11 @@ TEST_F(PenLayerTest, DrawLine) penLayer.drawLine(attr, 130, 77, 125, -22); attr.color = QNanoColor(0, 128, 0, 128); - attr.diameter = 10; + attr.diameter = 225; - penLayer.drawLine(attr, 152, -158, -228, 145); + penLayer.drawLine(attr, -225, 25, -175, -25); + + attr.diameter = 10; penLayer.drawLine(attr, -100, 139, 20, 72); attr.color = QNanoColor(255, 50, 200, 185); diff --git a/test/renderedtarget/renderedtarget_test.cpp b/test/renderedtarget/renderedtarget_test.cpp index 4b2ad13..dad1560 100644 --- a/test/renderedtarget/renderedtarget_test.cpp +++ b/test/renderedtarget/renderedtarget_test.cpp @@ -87,7 +87,9 @@ TEST_F(RenderedTargetTest, UpdateMethods) target.setStageModel(&stageModel); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); costume->setRotationCenterX(-23); costume->setRotationCenterY(72); costume->setBitmapResolution(2.5); @@ -282,7 +284,9 @@ TEST_F(RenderedTargetTest, UpdateMethods) // SVG costume = std::make_shared("", "", "svg"); std::string svgCostumeData = readFileStr("image.svg"); - costume->setData(svgCostumeData.size(), static_cast(svgCostumeData.data())); + char *svgData = (char *)malloc((svgCostumeData.size() + 1) * sizeof(char)); + memcpy(svgData, svgCostumeData.c_str(), (svgCostumeData.size() + 1) * sizeof(char)); + costume->setData(svgCostumeData.size(), static_cast(svgData)); costume->setRotationCenterX(25); costume->setRotationCenterY(-8); sprite.addCostume(costume); @@ -373,7 +377,9 @@ TEST_F(RenderedTargetTest, CpuRendering) EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(360)); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); sprite.addCostume(costume); target.loadCostumes(); target.updateCostume(costume.get()); @@ -759,7 +765,9 @@ TEST_F(RenderedTargetTest, GetBounds) target.setEngine(&engine); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); costume->setRotationCenterX(-15); costume->setRotationCenterY(48); costume->setBitmapResolution(3.25); @@ -850,7 +858,9 @@ TEST_F(RenderedTargetTest, GetFastBounds) target.setEngine(&engine); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); costume->setRotationCenterX(-15); costume->setRotationCenterY(48); costume->setBitmapResolution(3.25); @@ -925,7 +935,9 @@ TEST_F(RenderedTargetTest, TouchingClones) EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(360)); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); sprite.addCostume(costume); target.loadCostumes(); target.updateCostume(costume.get()); @@ -1140,7 +1152,9 @@ TEST_F(RenderedTargetTest, TouchingColor) EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(360)); auto costume = std::make_shared("", "", "png"); std::string costumeData = readFileStr("image.png"); - costume->setData(costumeData.size(), static_cast(costumeData.data())); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume->setData(costumeData.size(), static_cast(data)); sprite->addCostume(costume); target.loadCostumes(); target.updateCostume(costume.get()); diff --git a/test/skins/bitmapskin_test.cpp b/test/skins/bitmapskin_test.cpp index 6e5c936..484eeae 100644 --- a/test/skins/bitmapskin_test.cpp +++ b/test/skins/bitmapskin_test.cpp @@ -21,12 +21,16 @@ class BitmapSkinTest : public testing::Test Costume jpegCostume("", "", ""); std::string costumeData = readFileStr("image.jpg"); - jpegCostume.setData(costumeData.size(), costumeData.data()); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + jpegCostume.setData(costumeData.size(), data); m_jpegSkin = std::make_unique(&jpegCostume); Costume pngCostume("", "", ""); costumeData = readFileStr("image.png"); - pngCostume.setData(costumeData.size(), costumeData.data()); + data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + pngCostume.setData(costumeData.size(), data); m_pngSkin = std::make_unique(&pngCostume); } diff --git a/test/skins/svgskin_test.cpp b/test/skins/svgskin_test.cpp index 7f59952..780d3dd 100644 --- a/test/skins/svgskin_test.cpp +++ b/test/skins/svgskin_test.cpp @@ -21,7 +21,9 @@ class SVGSkinTest : public testing::Test Costume costume("", "", ""); std::string costumeData = readFileStr("image.svg"); - costume.setData(costumeData.size(), costumeData.data()); + char *data = (char *)malloc((costumeData.size() + 1) * sizeof(char)); + memcpy(data, costumeData.c_str(), (costumeData.size() + 1) * sizeof(char)); + costume.setData(costumeData.size(), data); m_skin = std::make_unique(&costume); } diff --git a/thirdparty/libqnanopainter/nanovg/nanovg.c b/thirdparty/libqnanopainter/nanovg/nanovg.c index d965990..83c1d0f 100644 --- a/thirdparty/libqnanopainter/nanovg/nanovg.c +++ b/thirdparty/libqnanopainter/nanovg/nanovg.c @@ -2345,7 +2345,7 @@ void nvgStroke(NVGcontext* ctx) { NVGstate* state = nvg__getState(ctx); float scale = nvg__getAverageScale(state->xform); - float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 200.0f); + float strokeWidth = state->strokeWidth * scale; NVGpaint strokePaint = state->stroke; const NVGpath* path; int i;