Skip to content

Commit ca00795

Browse files
authored
Merge pull request #156 from scratchcpp/rewrite_stamp
Rewrite stamp
2 parents c101e4c + fd8d695 commit ca00795

File tree

10 files changed

+117
-153
lines changed

10 files changed

+117
-153
lines changed

src/penlayer.cpp

Lines changed: 60 additions & 104 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,10 +33,13 @@ 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);
40+
41+
// Delete stamp FBO
42+
m_glF->glDeleteFramebuffers(1, &m_stampFbo);
3143
}
3244
}
3345

@@ -68,13 +80,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
6880
m_glF->initializeOpenGLFunctions();
6981
}
7082

71-
if (!m_blitter.isCreated()) {
72-
m_blitter.create();
73-
83+
if (m_vao == 0) {
7484
// 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-
};
85+
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 };
7886

7987
m_glF->glGenVertexArrays(1, &m_vao);
8088
m_glF->glGenBuffers(1, &m_vbo);
@@ -94,6 +102,9 @@ void PenLayer::setEngine(libscratchcpp::IEngine *newEngine)
94102

95103
m_glF->glBindVertexArray(0);
96104
m_glF->glBindBuffer(GL_ARRAY_BUFFER, 0);
105+
106+
// Create stamp FBO
107+
m_glF->glGenFramebuffers(1, &m_stampFbo);
97108
}
98109

99110
clear();
@@ -195,98 +206,81 @@ void scratchcpprender::PenLayer::drawLine(const PenAttributes &penAttributes, do
195206
update();
196207
}
197208

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-
*/
207209
void PenLayer::stamp(IRenderedTarget *target)
208210
{
209-
if (!target || !m_fbo || !m_texture.isValid() || !m_blitter.isCreated())
211+
if (!target || !m_fbo || !m_texture.isValid() || m_vao == 0 || m_vbo == 0)
210212
return;
211213

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;
214+
const float stageWidth = m_engine->stageWidth() * m_scale;
215+
const float stageHeight = m_engine->stageHeight() * m_scale;
216+
float x = 0;
217+
float y = 0;
218+
float angle = 180;
219+
float scaleX = 1;
220+
float scaleY = 1;
218221

219222
SpriteModel *spriteModel = target->spriteModel();
220223

221224
if (spriteModel) {
222225
libscratchcpp::Sprite *sprite = spriteModel->sprite();
223-
x = sprite->x();
224-
y = sprite->y();
225226

226227
switch (sprite->rotationStyle()) {
227228
case libscratchcpp::Sprite::RotationStyle::AllAround:
228-
angle = 90 - sprite->direction();
229+
angle = 270 - sprite->direction();
229230
break;
230231

231232
case libscratchcpp::Sprite::RotationStyle::LeftRight:
232-
mirror = (sprite->direction() < 0);
233+
scaleX = sgn(sprite->direction());
233234
break;
234235

235236
default:
236237
break;
237238
}
238239

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;
240+
scaleY = sprite->size() / 100;
241+
scaleX *= scaleY;
242+
}
246243

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

247+
libscratchcpp::Rect bounds = target->getFastBounds();
251248
const Texture &texture = target->cpuTexture();
252249

253250
if (!texture.isValid())
254251
return;
255252

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-
253+
const float textureScale = texture.width() / static_cast<float>(target->costumeWidth());
254+
const float skinWidth = texture.width();
255+
const float skinHeight = texture.height();
256+
257+
// Projection matrix
258+
QMatrix4x4 projectionMatrix;
259+
const float aspectRatio = skinHeight / skinWidth;
260+
projectionMatrix.ortho(1.0f, -1.0f, aspectRatio, -aspectRatio, 0.1f, 0.0f);
261+
projectionMatrix.scale(skinWidth / bounds.width() / m_scale, skinHeight / bounds.height() / m_scale);
262+
263+
// Model matrix
264+
// TODO: This should be calculated and cached by targets
265+
QMatrix4x4 modelMatrix;
266+
modelMatrix.rotate(angle, 0, 0, 1);
267+
modelMatrix.scale(scaleX / textureScale, aspectRatio * scaleY / textureScale);
266268
m_glF->glDisable(GL_SCISSOR_TEST);
267-
268-
// For some reason nothing is rendered without this
269-
// TODO: Find out why this is happening
270-
m_painter->beginFrame(m_fbo->width(), m_fbo->height());
271-
m_painter->stroke();
272-
m_painter->endFrame();
273-
274-
// Create a temporary FBO for graphic effects
275-
QOpenGLFramebufferObject tmpFbo(texture.size());
276-
m_painter->beginFrame(tmpFbo.width(), tmpFbo.height());
269+
m_glF->glDisable(GL_DEPTH_TEST);
277270

278271
// Create a FBO for the current texture
279-
unsigned int fbo;
280-
m_glF->glGenFramebuffers(1, &fbo);
281-
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
272+
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, m_stampFbo);
282273
m_glF->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle(), 0);
283274

284275
if (m_glF->glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
285276
qWarning() << "error: framebuffer incomplete (stamp " + target->scratchTarget()->name() + ")";
286-
m_glF->glDeleteFramebuffers(1, &fbo);
277+
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, 0);
287278
return;
288279
}
289280

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

@@ -298,62 +292,24 @@ void PenLayer::stamp(IRenderedTarget *target)
298292
m_glF->glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
299293

300294
// Render to the target framebuffer
301-
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, tmpFbo.handle());
295+
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, m_fbo->handle());
302296
shaderProgram->bind();
303297
m_glF->glBindVertexArray(m_vao);
304298
m_glF->glActiveTexture(GL_TEXTURE0);
305299
m_glF->glBindTexture(GL_TEXTURE_2D, texture.handle());
306300
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();
301+
shaderProgram->setUniformValue("u_projectionMatrix", projectionMatrix);
302+
shaderProgram->setUniformValue("u_modelMatrix", modelMatrix);
303+
m_glF->glDrawArrays(GL_TRIANGLES, 0, 6);
327304

328305
// Cleanup
329306
shaderProgram->release();
330307
m_glF->glBindVertexArray(0);
331308
m_glF->glBindBuffer(GL_ARRAY_BUFFER, 0);
332309
m_glF->glBindFramebuffer(GL_FRAMEBUFFER, 0);
333-
m_glF->glDeleteFramebuffers(1, &fbo);
334-
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();
355310

356311
m_glF->glEnable(GL_SCISSOR_TEST);
312+
m_glF->glEnable(GL_DEPTH_TEST);
357313

358314
m_textureDirty = true;
359315
m_boundsDirty = true;

src/penlayer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class PenLayer : public IPenLayer
6060
void updateTexture();
6161

6262
static std::unordered_map<libscratchcpp::IEngine *, IPenLayer *> m_projectPenLayers;
63+
static inline GLuint m_stampFbo = 0;
6364
bool m_antialiasingEnabled = true;
6465
libscratchcpp::IEngine *m_engine = nullptr;
6566
bool m_hqPen = false;
@@ -72,7 +73,6 @@ class PenLayer : public IPenLayer
7273
mutable CpuTextureManager m_textureManager;
7374
mutable bool m_boundsDirty = true;
7475
mutable libscratchcpp::Rect m_bounds;
75-
QOpenGLTextureBlitter m_blitter;
7676
GLuint m_vbo = 0;
7777
GLuint m_vao = 0;
7878
};

src/renderedtarget.cpp

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -683,56 +683,52 @@ void RenderedTarget::calculatePos()
683683
if (!m_skin || !m_costume || !m_engine)
684684
return;
685685

686-
if (isVisible() || m_stageModel) {
687-
double stageWidth = m_engine->stageWidth();
688-
double stageHeight = m_engine->stageHeight();
689-
setX(m_stageScale * (stageWidth / 2 + m_x - m_costume->rotationCenterX() * m_size / scale() / m_costume->bitmapResolution() * (m_mirrorHorizontally ? -1 : 1)));
690-
setY(m_stageScale * (stageHeight / 2 - m_y - m_costume->rotationCenterY() * m_size / scale() / m_costume->bitmapResolution()));
691-
qreal originX = m_costume->rotationCenterX() * m_stageScale * m_size / scale() / m_costume->bitmapResolution();
692-
qreal originY = m_costume->rotationCenterY() * m_stageScale * m_size / scale() / m_costume->bitmapResolution();
693-
setTransformOriginPoint(QPointF(originX, originY));
694-
695-
// Qt ignores the transform origin point if it's (0, 0),
696-
// so set the transform origin to top left in this case.
697-
if (originX == 0 && originY == 0)
698-
setTransformOrigin(QQuickItem::TopLeft);
699-
else
700-
setTransformOrigin(QQuickItem::Center);
701-
}
686+
double stageWidth = m_engine->stageWidth();
687+
double stageHeight = m_engine->stageHeight();
688+
setX(m_stageScale * (stageWidth / 2 + m_x - m_costume->rotationCenterX() * m_size / scale() / m_costume->bitmapResolution() * (m_mirrorHorizontally ? -1 : 1)));
689+
setY(m_stageScale * (stageHeight / 2 - m_y - m_costume->rotationCenterY() * m_size / scale() / m_costume->bitmapResolution()));
690+
qreal originX = m_costume->rotationCenterX() * m_stageScale * m_size / scale() / m_costume->bitmapResolution();
691+
qreal originY = m_costume->rotationCenterY() * m_stageScale * m_size / scale() / m_costume->bitmapResolution();
692+
setTransformOriginPoint(QPointF(originX, originY));
693+
694+
// Qt ignores the transform origin point if it's (0, 0),
695+
// so set the transform origin to top left in this case.
696+
if (originX == 0 && originY == 0)
697+
setTransformOrigin(QQuickItem::TopLeft);
698+
else
699+
setTransformOrigin(QQuickItem::Center);
702700

703701
m_transformedHullDirty = true;
704702
}
705703

706704
void RenderedTarget::calculateRotation()
707705
{
708-
if (isVisible()) {
709-
// Direction
710-
bool oldMirrorHorizontally = m_mirrorHorizontally;
706+
// Direction
707+
bool oldMirrorHorizontally = m_mirrorHorizontally;
711708

712-
switch (m_rotationStyle) {
713-
case Sprite::RotationStyle::AllAround:
714-
setRotation(m_direction - 90);
715-
m_mirrorHorizontally = (false);
709+
switch (m_rotationStyle) {
710+
case Sprite::RotationStyle::AllAround:
711+
setRotation(m_direction - 90);
712+
m_mirrorHorizontally = (false);
716713

717-
break;
714+
break;
718715

719-
case Sprite::RotationStyle::LeftRight: {
720-
setRotation(0);
721-
m_mirrorHorizontally = (m_direction < 0);
716+
case Sprite::RotationStyle::LeftRight: {
717+
setRotation(0);
718+
m_mirrorHorizontally = (m_direction < 0);
722719

723-
break;
724-
}
725-
726-
case Sprite::RotationStyle::DoNotRotate:
727-
setRotation(0);
728-
m_mirrorHorizontally = false;
729-
break;
720+
break;
730721
}
731722

732-
if (m_mirrorHorizontally != oldMirrorHorizontally)
733-
emit mirrorHorizontallyChanged();
723+
case Sprite::RotationStyle::DoNotRotate:
724+
setRotation(0);
725+
m_mirrorHorizontally = false;
726+
break;
734727
}
735728

729+
if (m_mirrorHorizontally != oldMirrorHorizontally)
730+
emit mirrorHorizontallyChanged();
731+
736732
m_transformedHullDirty = true;
737733
}
738734

src/shaders/sprite.vert

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
uniform mat4 u_projectionMatrix;
2+
uniform mat4 u_modelMatrix;
13
attribute vec2 a_position;
24
attribute vec2 a_texCoord;
35

46
varying vec2 v_texCoord;
57

68
void main() {
7-
gl_Position = vec4(a_position, 0.0, 1.0);
9+
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
810
v_texCoord = a_texCoord;
911
}

src/skin.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ Texture Skin::createAndPaintTexture(int width, int height)
5858
// Create final texture from the image
5959
auto texture = std::make_shared<QOpenGLTexture>(image);
6060
m_textures.push_back(texture);
61-
texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
62-
texture->setMagnificationFilter(QOpenGLTexture::Linear);
61+
texture->setMinificationFilter(QOpenGLTexture::Nearest);
62+
texture->setMagnificationFilter(QOpenGLTexture::Nearest);
63+
texture->bind();
64+
glF.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
65+
glF.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
66+
texture->release();
6367

6468
return Texture(texture->textureId(), width, height);
6569
}

0 commit comments

Comments
 (0)