Skip to content

Commit df9faa5

Browse files
committed
PenLayer: Rewrite stamp()
1 parent f33e005 commit df9faa5

File tree

5 files changed

+52
-95
lines changed

5 files changed

+52
-95
lines changed

src/penlayer.cpp

Lines changed: 50 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@
1111

1212
using namespace scratchcpprender;
1313

14+
static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
15+
1416
std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> PenLayer::m_projectPenLayers;
1517

18+
// TODO: Move this to a separate class
19+
template<typename T>
20+
short sgn(T x)
21+
{
22+
return (T(0) < x) - (x < T(0));
23+
}
24+
1625
PenLayer::PenLayer(QNanoQuickItem *parent) :
1726
IPenLayer(parent)
1827
{
@@ -24,7 +33,7 @@ PenLayer::~PenLayer()
2433
if (m_engine)
2534
m_projectPenLayers.erase(m_engine);
2635

27-
if (m_blitter.isCreated()) {
36+
if (m_vao != 0) {
2837
// Delete vertex array and buffer
2938
m_glF->glDeleteVertexArrays(1, &m_vao);
3039
m_glF->glDeleteBuffers(1, &m_vbo);
@@ -68,13 +77,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
6877
m_glF->initializeOpenGLFunctions();
6978
}
7079

71-
if (!m_blitter.isCreated()) {
72-
m_blitter.create();
73-
80+
if (m_vao == 0) {
7481
// Set up VBO and VAO
75-
float vertices[] = {
76-
-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, 1.0f,
77-
};
82+
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 };
7883

7984
m_glF->glGenVertexArrays(1, &m_vao);
8085
m_glF->glGenBuffers(1, &m_vbo);
@@ -195,74 +200,65 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
195200
update();
196201
}
197202

198-
/*
199-
* A brief description of how stamping is implemented:
200-
* 1. Get rotation, size and coordinates and translate them.
201-
* 2. Draw the texture onto a temporary texture using shaders.
202-
* 3. Blit the resulting texture to a FBO with a square texture (required for rotation).
203-
* 4. Blit the resulting texture to the pen layer using QOpenGLTextureBlitter with transform.
204-
*
205-
* If you think this is too complicated, contributions are welcome!
206-
*/
207203
void PenLayer::stamp(IRenderedTarget *target)
208204
{
209-
if (!target || !m_fbo || !m_texture.isValid() || !m_blitter.isCreated())
205+
if (!target || !m_fbo || !m_texture.isValid() || m_vao == 0 || m_vbo == 0)
210206
return;
211207

212-
double x = 0;
213-
double y = 0;
214-
double angle = 0;
215-
double scale = 1;
216-
bool mirror = false;
217-
std::shared_ptr<libscratchcpp::Costume> costume;
208+
const float stageWidth = m_engine->stageWidth() * m_scale;
209+
const float stageHeight = m_engine->stageHeight() * m_scale;
210+
float x = 0;
211+
float y = 0;
212+
float angle = 180;
213+
float scaleX = 1;
214+
float scaleY = 1;
218215

219216
SpriteModel *spriteModel = target->spriteModel();
220217

221218
if (spriteModel) {
222219
libscratchcpp::Sprite *sprite = spriteModel->sprite();
223-
x = sprite->x();
224-
y = sprite->y();
225220

226221
switch (sprite->rotationStyle()) {
227222
case libscratchcpp::Sprite::RotationStyle::AllAround:
228-
angle = 90 - sprite->direction();
223+
angle = 270 - sprite->direction();
229224
break;
230225

231226
case libscratchcpp::Sprite::RotationStyle::LeftRight:
232-
mirror = (sprite->direction() < 0);
227+
scaleX = sgn(sprite->direction());
233228
break;
234229

235230
default:
236231
break;
237232
}
238233

239-
scale = sprite->size() / 100;
240-
costume = sprite->currentCostume();
241-
} else
242-
costume = target->stageModel()->stage()->currentCostume();
243-
244-
// Apply scale (HQ pen)
245-
scale *= m_scale;
234+
scaleY = sprite->size() / 100;
235+
scaleX *= scaleY;
236+
}
246237

247-
const double bitmapRes = costume->bitmapResolution();
248-
const double centerX = costume->rotationCenterX() / bitmapRes;
249-
const double centerY = costume->rotationCenterY() / bitmapRes;
238+
scaleX *= m_scale;
239+
scaleY *= m_scale;
250240

241+
libscratchcpp::Rect bounds = target->getFastBounds();
251242
const Texture &texture = target->cpuTexture();
252243

253244
if (!texture.isValid())
254245
return;
255246

256-
const double textureScale = texture.width() / static_cast<double>(target->costumeWidth());
257-
258-
// Apply scale (HQ pen)
259-
x *= m_scale;
260-
y *= m_scale;
261-
262-
// Translate the coordinates
263-
x = std::floor(x + m_texture.width() / 2.0);
264-
y = std::floor(-y + m_texture.height() / 2.0);
265-
247+
const float textureScale = texture.width() / static_cast<float>(target->costumeWidth());
248+
const float skinWidth = texture.width();
249+
const float skinHeight = texture.height();
250+
251+
// Projection matrix
252+
QMatrix4x4 projectionMatrix;
253+
const float aspectRatio = skinHeight / skinWidth;
254+
projectionMatrix.ortho(1.0f, -1.0f, aspectRatio, -aspectRatio, 0.1f, 0.0f);
255+
projectionMatrix.scale(skinWidth / bounds.width() / m_scale, skinHeight / bounds.height() / m_scale);
256+
257+
// Model matrix
258+
// TODO: This should be calculated and cached by targets
259+
QMatrix4x4 modelMatrix;
260+
modelMatrix.rotate(angle, 0, 0, 1);
261+
modelMatrix.scale(scaleX / textureScale, aspectRatio * scaleY / textureScale);
266262
m_glF->glDisable(GL_SCISSOR_TEST);
267263

268264
// For some reason nothing is rendered without this
@@ -271,10 +267,6 @@ void PenLayer::stamp(IRenderedTarget *target)
271267
m_painter->stroke();
272268
m_painter->endFrame();
273269

274-
// Create a temporary FBO for graphic effects
275-
QOpenGLFramebufferObject tmpFbo(texture.size());
276-
m_painter->beginFrame(tmpFbo.width(), tmpFbo.height());
277-
278270
// Create a FBO for the current texture
279271
unsigned int fbo;
280272
m_glF->glGenFramebuffers(1, &fbo);
@@ -287,6 +279,9 @@ void PenLayer::stamp(IRenderedTarget *target)
287279
return;
288280
}
289281

282+
// Set viewport
283+
m_glF->glViewport((stageWidth / 2) + bounds.left() * m_scale, (stageHeight / 2) + bounds.bottom() * m_scale, bounds.width() * m_scale, bounds.height() * m_scale);
284+
290285
// Get the shader program for the current set of effects
291286
ShaderManager *shaderManager = ShaderManager::instance();
292287

@@ -298,32 +293,15 @@ void PenLayer::stamp(IRenderedTarget *target)
298293
m_glF->glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
299294

300295
// Render to the target framebuffer
301-
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, tmpFbo.handle());
296+
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
302297
shaderProgram->bind();
303298
m_glF->glBindVertexArray(m_vao);
304299
m_glF->glActiveTexture(GL_TEXTURE0);
305300
m_glF->glBindTexture(GL_TEXTURE_2D, texture.handle());
306301
shaderManager->setUniforms(shaderProgram, 0, texture.size(), effects); // set texture and effect uniforms
307-
m_glF->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
308-
309-
m_painter->endFrame();
310-
311-
// Resize to square (for rotation)
312-
const double dim = std::max(tmpFbo.width(), tmpFbo.height());
313-
QOpenGLFramebufferObject resizeFbo(dim, dim);
314-
resizeFbo.bind();
315-
m_painter->beginFrame(dim, dim);
316-
317-
const QRect resizeRect(QPoint(0, 0), tmpFbo.size());
318-
const QMatrix4x4 matrix = QOpenGLTextureBlitter::targetTransform(resizeRect, QRect(QPoint(0, 0), resizeFbo.size()));
319-
m_glF->glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
320-
m_glF->glClear(GL_COLOR_BUFFER_BIT);
321-
m_blitter.bind();
322-
m_blitter.blit(tmpFbo.texture(), matrix, QOpenGLTextureBlitter::OriginBottomLeft);
323-
m_blitter.release();
324-
325-
m_painter->endFrame();
326-
resizeFbo.release();
302+
shaderProgram->setUniformValue("u_projectionMatrix", projectionMatrix);
303+
shaderProgram->setUniformValue("u_modelMatrix", modelMatrix);
304+
m_glF->glDrawArrays(GL_TRIANGLES, 0, 6);
327305

328306
// Cleanup
329307
shaderProgram->release();
@@ -332,27 +310,6 @@ void PenLayer::stamp(IRenderedTarget *target)
332310
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, 0);
333311
m_glF->glDeleteFramebuffers(1, &fbo);
334312

335-
// Transform
336-
const double width = resizeFbo.width() / textureScale;
337-
const double height = resizeFbo.height() / textureScale;
338-
QRectF targetRect(QPoint(x, y), QSizeF(width, height));
339-
QTransform transform = QOpenGLTextureBlitter::targetTransform(targetRect, QRect(QPoint(centerX, centerY), m_fbo->size())).toTransform();
340-
const double dx = 2 * (centerX - width / 2.0) / width;
341-
const double dy = -2 * (centerY - height / 2.0) / height;
342-
transform.translate(dx, dy);
343-
transform.rotate(angle);
344-
transform.scale(scale * (mirror ? -1 : 1), scale);
345-
transform.translate(-dx, -dy);
346-
347-
// Blit
348-
m_fbo->bind();
349-
m_painter->beginFrame(m_fbo->width(), m_fbo->height());
350-
m_blitter.bind();
351-
m_blitter.blit(resizeFbo.texture(), transform, QOpenGLTextureBlitter::OriginBottomLeft);
352-
m_blitter.release();
353-
m_painter->endFrame();
354-
m_fbo->release();
355-
356313
m_glF->glEnable(GL_SCISSOR_TEST);
357314

358315
m_textureDirty = true;

src/penlayer.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ class PenLayer : public IPenLayer
7272
mutable CpuTextureManager m_textureManager;
7373
mutable bool m_boundsDirty = true;
7474
mutable libscratchcpp::Rect m_bounds;
75-
QOpenGLTextureBlitter m_blitter;
7675
GLuint m_vbo = 0;
7776
GLuint m_vao = 0;
7877
};

test/penlayer/penlayer_test.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,8 @@ TEST_F(PenLayerTest, Stamp)
373373
loader.setFileName("stamp_env.sb3");
374374
loader.start(); // wait until it loads
375375

376+
EXPECT_CALL(engine, stageWidth()).WillRepeatedly(Return(480));
377+
EXPECT_CALL(engine, stageHeight()).WillRepeatedly(Return(360));
376378
std::vector<std::unique_ptr<RenderedTarget>> targets;
377379
StageModel *stage = loader.stage();
378380
targets.push_back(std::make_unique<RenderedTarget>());
@@ -411,7 +413,6 @@ TEST_F(PenLayerTest, Stamp)
411413

412414
// Test HQ pen
413415
penLayer.clear();
414-
EXPECT_CALL(engine, stageWidth()).Times(3).WillRepeatedly(Return(480));
415416
penLayer.setHqPen(true);
416417
penLayer.setWidth(720);
417418
penLayer.setHeight(540);

test/stamp.png

2.35 KB
Loading

test/stamp_hq.png

133 KB
Loading

0 commit comments

Comments
 (0)