Skip to content

Commit 477d468

Browse files
committed
Add target hull points
1 parent 108fd32 commit 477d468

File tree

7 files changed

+148
-2
lines changed

7 files changed

+148
-2
lines changed

ScratchCPPGui/irenderedtarget.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class IRenderedTarget : public QNanoQuickItem
6363

6464
virtual bool isSvg() const = 0;
6565
virtual void paintSvg(QNanoPainter *painter) = 0;
66+
67+
virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
68+
virtual const std::vector<QPointF> &hullPoints() const = 0;
6669
};
6770

6871
} // namespace scratchcppgui

ScratchCPPGui/renderedtarget.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,66 @@ void RenderedTarget::paintSvg(QNanoPainter *painter)
303303
context->makeCurrent(oldSurface);
304304
}
305305

306+
void RenderedTarget::updateHullPoints(QOpenGLFramebufferObject *fbo)
307+
{
308+
if (m_stageModel)
309+
return; // hull points are useless for the stage
310+
311+
Q_ASSERT(fbo);
312+
int width = fbo->width();
313+
int height = fbo->height();
314+
m_hullPoints.clear();
315+
m_hullPoints.reserve(width * height);
316+
317+
// Blit multisampled FBO to a custom FBO
318+
QOpenGLFramebufferObject customFbo(fbo->size());
319+
glBindFramebuffer(GL_READ_FRAMEBUFFER_EXT, fbo->handle());
320+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, customFbo.handle());
321+
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
322+
glBindFramebuffer(GL_FRAMEBUFFER_EXT, customFbo.handle());
323+
324+
// Read pixels from framebuffer
325+
size_t size = width * height * 4;
326+
GLubyte *pixelData = new GLubyte[size];
327+
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
328+
glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
329+
fbo->bind();
330+
331+
// Flip vertically
332+
int rowSize = width * 4;
333+
GLubyte *tempRow = new GLubyte[rowSize];
334+
335+
for (size_t i = 0; i < height / 2; ++i) {
336+
size_t topRowIndex = i * rowSize;
337+
size_t bottomRowIndex = (height - 1 - i) * rowSize;
338+
339+
// Swap rows
340+
memcpy(tempRow, &pixelData[topRowIndex], rowSize);
341+
memcpy(&pixelData[topRowIndex], &pixelData[bottomRowIndex], rowSize);
342+
memcpy(&pixelData[bottomRowIndex], tempRow, rowSize);
343+
}
344+
345+
delete[] tempRow;
346+
347+
// Fill hull points vector
348+
for (int y = 0; y < height; y++) {
349+
for (int x = 0; x < width; x++) {
350+
int index = (y * width + x) * 4; // RGBA channels
351+
352+
// Check alpha channel
353+
if (pixelData[index + 3] > 0)
354+
m_hullPoints.push_back(QPointF(x, y));
355+
}
356+
}
357+
358+
delete[] pixelData;
359+
}
360+
361+
const std::vector<QPointF> &RenderedTarget::hullPoints() const
362+
{
363+
return m_hullPoints;
364+
}
365+
306366
void RenderedTarget::calculateSize(Target *target, double costumeWidth, double costumeHeight)
307367
{
308368
if (m_costume) {

ScratchCPPGui/renderedtarget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class RenderedTarget : public IRenderedTarget
5959
bool isSvg() const override;
6060
void paintSvg(QNanoPainter *painter) override;
6161

62+
void updateHullPoints(QOpenGLFramebufferObject *fbo) override;
63+
const std::vector<QPointF> &hullPoints() const override;
64+
6265
signals:
6366
void engineChanged();
6467
void stageModelChanged();
@@ -101,6 +104,7 @@ class RenderedTarget : public IRenderedTarget
101104
double m_originY = 0;
102105
qreal m_maximumWidth = std::numeric_limits<double>::infinity();
103106
qreal m_maximumHeight = std::numeric_limits<double>::infinity();
107+
std::vector<QPointF> m_hullPoints;
104108
};
105109

106110
} // namespace scratchcppgui

ScratchCPPGui/targetpainter.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,30 @@ void TargetPainter::paint(QNanoPainter *painter)
2626
if (m_target->isSvg())
2727
m_target->paintSvg(painter);
2828
else {
29+
QOpenGLContext *context = QOpenGLContext::currentContext();
30+
Q_ASSERT(context);
31+
32+
if (!context)
33+
return;
34+
35+
QOffscreenSurface surface;
36+
surface.setFormat(context->format());
37+
surface.create();
38+
Q_ASSERT(surface.isValid());
39+
40+
QSurface *oldSurface = context->surface();
41+
context->makeCurrent(&surface);
42+
43+
painter->beginFrame(width, height);
2944
QNanoImage image = QNanoImage::fromCache(painter, m_target->bitmapBuffer(), m_target->bitmapUniqueKey());
3045
painter->drawImage(image, 0, 0, width, height);
46+
painter->endFrame();
47+
48+
context->doneCurrent();
49+
context->makeCurrent(oldSurface);
3150
}
3251

52+
m_target->updateHullPoints(framebufferObject());
3353
m_target->unlockCostume();
3454
}
3555

test/mocks/renderedtargetmock.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class RenderedTargetMock : public IRenderedTarget
4444
MOCK_METHOD(bool, isSvg, (), (const, override));
4545
MOCK_METHOD(void, paintSvg, (QNanoPainter *), (override));
4646

47+
MOCK_METHOD(void, updateHullPoints, (QOpenGLFramebufferObject *), (override));
48+
MOCK_METHOD(const std::vector<QPointF> &, hullPoints, (), (const, override));
49+
4750
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
4851
};
4952

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,60 @@ TEST_F(RenderedTargetTest, PaintSvg)
468468
context.doneCurrent();
469469
}
470470

471+
TEST_F(RenderedTargetTest, HullPoints)
472+
{
473+
EngineMock engine;
474+
Sprite sprite;
475+
SpriteModel model;
476+
model.init(&sprite);
477+
478+
RenderedTarget target;
479+
target.setEngine(&engine);
480+
target.setSpriteModel(&model);
481+
482+
// Create OpenGL context
483+
QOpenGLContext context;
484+
QOffscreenSurface surface;
485+
createContextAndSurface(&context, &surface);
486+
487+
// Create a painter
488+
QNanoPainter painter;
489+
490+
QOpenGLFramebufferObjectFormat format;
491+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
492+
493+
// Begin painting
494+
QOpenGLFramebufferObject fbo(4, 6, format);
495+
fbo.bind();
496+
painter.beginFrame(fbo.width(), fbo.height());
497+
498+
// Paint
499+
QNanoImage image = QNanoImage::fromCache(&painter, "image.png");
500+
painter.drawImage(image, 0, 0);
501+
painter.endFrame();
502+
503+
// Test hull points
504+
target.updateHullPoints(&fbo);
505+
ASSERT_EQ(target.hullPoints(), std::vector<QPointF>({ { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } }));
506+
507+
// Begin painting (multisampled)
508+
format.setSamples(16);
509+
QOpenGLFramebufferObject fboMultiSampled(4, 6, format);
510+
fboMultiSampled.bind();
511+
painter.beginFrame(fboMultiSampled.width(), fboMultiSampled.height());
512+
513+
// Paint (multisampled)
514+
painter.drawImage(image, 0, 0);
515+
painter.endFrame();
516+
517+
// Test hull points (this is undefined with multisampling, so we just check if there are any hull points)
518+
ASSERT_FALSE(target.hullPoints().empty());
519+
520+
// Release
521+
fbo.release();
522+
context.doneCurrent();
523+
}
524+
471525
TEST_F(RenderedTargetTest, Engine)
472526
{
473527
RenderedTarget target;

test/targetpainter/targetpainter_test.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
5757

5858
// Begin painting reference
5959
QNanoPainter refPainter;
60-
QOpenGLFramebufferObject refFbo(100, 100, format);
60+
QOpenGLFramebufferObject refFbo(40, 60, format);
6161
refFbo.bind();
6262
refPainter.beginFrame(refFbo.width(), refFbo.height());
6363

@@ -67,7 +67,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
6767
refPainter.endFrame();
6868

6969
// Begin painting
70-
QOpenGLFramebufferObject fbo(100, 100, format);
70+
QOpenGLFramebufferObject fbo(40, 60, format);
7171
fbo.bind();
7272
painter.beginFrame(fbo.width(), fbo.height());
7373

@@ -79,6 +79,7 @@ TEST_F(TargetPainterTest, PaintBitmap)
7979
EXPECT_CALL(target, bitmapBuffer()).WillOnce(Return(&buffer));
8080
static const QString uniqueKey("abc");
8181
EXPECT_CALL(target, bitmapUniqueKey()).WillOnce(ReturnRef(uniqueKey));
82+
EXPECT_CALL(target, updateHullPoints);
8283
EXPECT_CALL(target, unlockCostume());
8384
targetPainter.paint(&painter);
8485
painter.endFrame();
@@ -110,6 +111,7 @@ TEST_F(TargetPainterTest, PaintSvg)
110111
EXPECT_CALL(target, height()).WillOnce(Return(60));
111112
EXPECT_CALL(target, isSvg()).WillOnce(Return(true));
112113
EXPECT_CALL(target, paintSvg(&painter));
114+
EXPECT_CALL(target, updateHullPoints);
113115
EXPECT_CALL(target, unlockCostume());
114116
targetPainter.paint(&painter);
115117

0 commit comments

Comments
 (0)