1
1
// SPDX-License-Identifier: LGPL-3.0-or-later
2
2
3
+ #include < scratchcpp/costume.h>
4
+
3
5
#include " penlayer.h"
4
6
#include " penlayerpainter.h"
5
7
#include " penattributes.h"
8
+ #include " irenderedtarget.h"
9
+ #include " spritemodel.h"
10
+ #include " stagemodel.h"
6
11
7
12
using namespace scratchcpprender ;
8
13
@@ -18,6 +23,12 @@ PenLayer::~PenLayer()
18
23
{
19
24
if (m_engine)
20
25
m_projectPenLayers.erase (m_engine);
26
+
27
+ if (m_blitter.isCreated ()) {
28
+ // Delete vertex array and buffer
29
+ m_glF->glDeleteVertexArrays (1 , &m_vao);
30
+ m_glF->glDeleteBuffers (1 , &m_vbo);
31
+ }
21
32
}
22
33
23
34
bool PenLayer::antialiasingEnabled () const
@@ -56,6 +67,39 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
56
67
if (!m_painter)
57
68
m_painter = std::make_unique<QNanoPainter>();
58
69
70
+ if (!m_glF) {
71
+ m_glF = std::make_unique<QOpenGLExtraFunctions>();
72
+ m_glF->initializeOpenGLFunctions ();
73
+ }
74
+
75
+ if (!m_blitter.isCreated ()) {
76
+ m_blitter.create ();
77
+
78
+ // Set up VBO and VAO
79
+ float vertices[] = {
80
+ -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 ,
81
+ };
82
+
83
+ m_glF->glGenVertexArrays (1 , &m_vao);
84
+ m_glF->glGenBuffers (1 , &m_vbo);
85
+
86
+ m_glF->glBindVertexArray (m_vao);
87
+
88
+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
89
+ m_glF->glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
90
+
91
+ // Position attribute
92
+ m_glF->glVertexAttribPointer (0 , 2 , GL_FLOAT, GL_FALSE, 4 * sizeof (float ), (void *)0 );
93
+ m_glF->glEnableVertexAttribArray (0 );
94
+
95
+ // Texture coordinate attribute
96
+ m_glF->glVertexAttribPointer (1 , 2 , GL_FLOAT, GL_FALSE, 4 * sizeof (float ), (void *)(2 * sizeof (float )));
97
+ m_glF->glEnableVertexAttribArray (1 );
98
+
99
+ m_glF->glBindVertexArray (0 );
100
+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
101
+ }
102
+
59
103
clear ();
60
104
}
61
105
@@ -67,11 +111,6 @@ void scratchcpprender::PenLayer::clear()
67
111
if (!m_fbo)
68
112
return ;
69
113
70
- if (!m_glF) {
71
- m_glF = std::make_unique<QOpenGLFunctions>();
72
- m_glF->initializeOpenGLFunctions ();
73
- }
74
-
75
114
m_fbo->bind ();
76
115
m_glF->glDisable (GL_SCISSOR_TEST);
77
116
m_glF->glClearColor (0 .0f , 0 .0f , 0 .0f , 0 .0f );
@@ -138,6 +177,165 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
138
177
update ();
139
178
}
140
179
180
+ /*
181
+ * A brief description of how stamping is implemented:
182
+ * 1. Get rotation, size and coordinates and translate them.
183
+ * 2. Draw the texture onto a temporary texture using shaders.
184
+ * 3. Blit the resulting texture to a FBO with a square texture (required for rotation).
185
+ * 4. Blit the resulting texture to the pen layer using QOpenGLTextureBlitter with transform.
186
+ *
187
+ * If you think this is too complicated, contributions are welcome!
188
+ */
189
+ void PenLayer::stamp (IRenderedTarget *target)
190
+ {
191
+ if (!target || !m_fbo || !m_texture.isValid () || !m_blitter.isCreated ())
192
+ return ;
193
+
194
+ double x = 0 ;
195
+ double y = 0 ;
196
+ double angle = 0 ;
197
+ double scale = 1 ;
198
+ bool mirror = false ;
199
+ std::shared_ptr<libscratchcpp::Costume> costume;
200
+
201
+ SpriteModel *spriteModel = target->spriteModel ();
202
+
203
+ if (spriteModel) {
204
+ libscratchcpp::Sprite *sprite = spriteModel->sprite ();
205
+ x = sprite->x ();
206
+ y = sprite->y ();
207
+
208
+ switch (sprite->rotationStyle ()) {
209
+ case libscratchcpp::Sprite::RotationStyle::AllAround:
210
+ angle = 90 - sprite->direction ();
211
+ break ;
212
+
213
+ case libscratchcpp::Sprite::RotationStyle::LeftRight:
214
+ mirror = (sprite->direction () < 0 );
215
+ break ;
216
+
217
+ default :
218
+ break ;
219
+ }
220
+
221
+ scale = sprite->size () / 100 ;
222
+ costume = sprite->currentCostume ();
223
+ } else
224
+ costume = target->stageModel ()->stage ()->currentCostume ();
225
+
226
+ const double bitmapRes = costume->bitmapResolution ();
227
+ const double centerX = costume->rotationCenterX () / bitmapRes;
228
+ const double centerY = costume->rotationCenterY () / bitmapRes;
229
+
230
+ const Texture &texture = target->cpuTexture ();
231
+
232
+ if (!texture.isValid ())
233
+ return ;
234
+
235
+ const double textureScale = texture.width () / static_cast <double >(target->costumeWidth ());
236
+
237
+ // Translate the coordinates
238
+ // TODO: Apply scale (HQ pen)
239
+ x = std::floor (x + m_texture.width () / 2.0 );
240
+ y = std::floor (-y + m_texture.height () / 2.0 );
241
+
242
+ m_glF->glDisable (GL_SCISSOR_TEST);
243
+
244
+ // For some reason nothing is rendered without this
245
+ // TODO: Find out why this is happening
246
+ m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
247
+ m_painter->stroke ();
248
+ m_painter->endFrame ();
249
+
250
+ // Create a temporary FBO for graphic effects
251
+ QOpenGLFramebufferObject tmpFbo (texture.size ());
252
+ m_painter->beginFrame (tmpFbo.width (), tmpFbo.height ());
253
+
254
+ // Create a FBO for the current texture
255
+ unsigned int fbo;
256
+ m_glF->glGenFramebuffers (1 , &fbo);
257
+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, fbo);
258
+ m_glF->glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle (), 0 );
259
+
260
+ if (m_glF->glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
261
+ qWarning () << " error: framebuffer incomplete (stamp " + target->scratchTarget ()->name () + " )" ;
262
+ m_glF->glDeleteFramebuffers (1 , &fbo);
263
+ return ;
264
+ }
265
+
266
+ // Get the shader program for the current set of effects
267
+ ShaderManager *shaderManager = ShaderManager::instance ();
268
+
269
+ const auto &effects = target->graphicEffects ();
270
+ QOpenGLShaderProgram *shaderProgram = shaderManager->getShaderProgram (effects);
271
+ Q_ASSERT (shaderProgram);
272
+ Q_ASSERT (shaderProgram->isLinked ());
273
+
274
+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, m_vbo);
275
+
276
+ // Render to the target framebuffer
277
+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, tmpFbo.handle ());
278
+ shaderProgram->bind ();
279
+ m_glF->glBindVertexArray (m_vao);
280
+ m_glF->glActiveTexture (GL_TEXTURE0);
281
+ m_glF->glBindTexture (GL_TEXTURE_2D, texture.handle ());
282
+ shaderManager->setUniforms (shaderProgram, 0 , effects); // set texture and effect uniforms
283
+ m_glF->glDrawArrays (GL_TRIANGLE_STRIP, 0 , 4 );
284
+
285
+ m_painter->endFrame ();
286
+
287
+ // Resize to square (for rotation)
288
+ const double dim = std::max (tmpFbo.width (), tmpFbo.height ());
289
+ QOpenGLFramebufferObject resizeFbo (dim, dim);
290
+ resizeFbo.bind ();
291
+ m_painter->beginFrame (dim, dim);
292
+
293
+ const QRect resizeRect (QPoint (0 , 0 ), tmpFbo.size ());
294
+ const QMatrix4x4 matrix = QOpenGLTextureBlitter::targetTransform (resizeRect, QRect (QPoint (0 , 0 ), resizeFbo.size ()));
295
+ m_glF->glClearColor (0 .0f , 0 .0f , 0 .0f , 0 .0f );
296
+ m_glF->glClear (GL_COLOR_BUFFER_BIT);
297
+ m_blitter.bind ();
298
+ m_blitter.blit (tmpFbo.texture (), matrix, QOpenGLTextureBlitter::OriginBottomLeft);
299
+ m_blitter.release ();
300
+
301
+ m_painter->endFrame ();
302
+ resizeFbo.release ();
303
+
304
+ // Cleanup
305
+ shaderProgram->release ();
306
+ m_glF->glBindVertexArray (0 );
307
+ m_glF->glBindBuffer (GL_ARRAY_BUFFER, 0 );
308
+ m_glF->glBindFramebuffer (GL_FRAMEBUFFER, 0 );
309
+ m_glF->glDeleteFramebuffers (1 , &fbo);
310
+
311
+ // Transform
312
+ const double width = resizeFbo.width () / textureScale;
313
+ const double height = resizeFbo.height () / textureScale;
314
+ QRectF targetRect (QPoint (x, y), QSizeF (width, height));
315
+ QTransform transform = QOpenGLTextureBlitter::targetTransform (targetRect, QRect (QPoint (centerX, centerY), m_fbo->size ())).toTransform ();
316
+ const double dx = 2 * (centerX - width / 2.0 ) / width;
317
+ const double dy = -2 * (centerY - height / 2.0 ) / height;
318
+ transform.translate (dx, dy);
319
+ transform.rotate (angle);
320
+ transform.scale (scale * (mirror ? -1 : 1 ), scale);
321
+ transform.translate (-dx, -dy);
322
+
323
+ // Blit
324
+ m_fbo->bind ();
325
+ m_painter->beginFrame (m_fbo->width (), m_fbo->height ());
326
+ m_blitter.bind ();
327
+ m_blitter.blit (resizeFbo.texture (), transform, QOpenGLTextureBlitter::OriginBottomLeft);
328
+ m_blitter.release ();
329
+ m_painter->endFrame ();
330
+ m_fbo->release ();
331
+
332
+ m_glF->glEnable (GL_SCISSOR_TEST);
333
+
334
+ m_textureDirty = true ;
335
+ m_boundsDirty = true ;
336
+ update ();
337
+ }
338
+
141
339
QOpenGLFramebufferObject *PenLayer::framebufferObject () const
142
340
{
143
341
return m_fbo.get ();
0 commit comments