1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qsgdefaultspritenode_p.h" |
41 | |
42 | #include <QtQuick/QSGMaterial> |
43 | #include <QtGui/QOpenGLShaderProgram> |
44 | |
45 | QT_BEGIN_NAMESPACE |
46 | |
47 | struct SpriteVertex { |
48 | float x; |
49 | float y; |
50 | float tx; |
51 | float ty; |
52 | }; |
53 | |
54 | struct SpriteVertices { |
55 | SpriteVertex v1; |
56 | SpriteVertex v2; |
57 | SpriteVertex v3; |
58 | SpriteVertex v4; |
59 | }; |
60 | |
61 | class QQuickSpriteMaterial : public QSGMaterial |
62 | { |
63 | public: |
64 | QQuickSpriteMaterial(); |
65 | ~QQuickSpriteMaterial(); |
66 | QSGMaterialType *type() const override { static QSGMaterialType type; return &type; } |
67 | QSGMaterialShader *createShader() const override; |
68 | int compare(const QSGMaterial *other) const override |
69 | { |
70 | return this - static_cast<const QQuickSpriteMaterial *>(other); |
71 | } |
72 | |
73 | QSGTexture *texture = nullptr; |
74 | |
75 | float animT = 0.0f; |
76 | float animX1 = 0.0f; |
77 | float animY1 = 0.0f; |
78 | float animX2 = 0.0f; |
79 | float animY2 = 0.0f; |
80 | float animW = 1.0f; |
81 | float animH = 1.0f; |
82 | }; |
83 | |
84 | QQuickSpriteMaterial::QQuickSpriteMaterial() |
85 | { |
86 | setFlag(flags: Blending, on: true); |
87 | setFlag(flags: SupportsRhiShader, on: true); |
88 | } |
89 | |
90 | QQuickSpriteMaterial::~QQuickSpriteMaterial() |
91 | { |
92 | delete texture; |
93 | } |
94 | |
95 | class SpriteMaterialShader : public QSGMaterialShader |
96 | { |
97 | public: |
98 | SpriteMaterialShader() |
99 | { |
100 | setShaderSourceFile(type: QOpenGLShader::Vertex, QStringLiteral(":/qt-project.org/scenegraph/shaders/sprite.vert" )); |
101 | setShaderSourceFile(type: QOpenGLShader::Fragment, QStringLiteral(":/qt-project.org/scenegraph/shaders/sprite.frag" )); |
102 | } |
103 | |
104 | void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *) override |
105 | { |
106 | QQuickSpriteMaterial *m = static_cast<QQuickSpriteMaterial *>(newEffect); |
107 | m->texture->bind(); |
108 | |
109 | program()->setUniformValue(location: m_opacity_id, value: state.opacity()); |
110 | program()->setUniformValue(location: m_animData_id, x: m->animW, y: m->animH, z: m->animT); |
111 | program()->setUniformValue(location: m_animPos_id, x: m->animX1, y: m->animY1, z: m->animX2, w: m->animY2); |
112 | |
113 | if (state.isMatrixDirty()) |
114 | program()->setUniformValue(location: m_matrix_id, value: state.combinedMatrix()); |
115 | } |
116 | |
117 | void initialize() override { |
118 | m_matrix_id = program()->uniformLocation(name: "qt_Matrix" ); |
119 | m_opacity_id = program()->uniformLocation(name: "qt_Opacity" ); |
120 | m_animData_id = program()->uniformLocation(name: "animData" ); |
121 | m_animPos_id = program()->uniformLocation(name: "animPos" ); |
122 | } |
123 | |
124 | char const *const *attributeNames() const override { |
125 | static const char *attr[] = { |
126 | "vPos" , |
127 | "vTex" , |
128 | nullptr |
129 | }; |
130 | return attr; |
131 | } |
132 | |
133 | int m_matrix_id; |
134 | int m_opacity_id; |
135 | int m_animData_id; |
136 | int m_animPos_id; |
137 | }; |
138 | |
139 | class SpriteMaterialRhiShader : public QSGMaterialRhiShader |
140 | { |
141 | public: |
142 | SpriteMaterialRhiShader(); |
143 | |
144 | bool updateUniformData(RenderState &state, |
145 | QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; |
146 | void updateSampledImage(RenderState &state, int binding, QSGTexture **texture, |
147 | QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; |
148 | }; |
149 | |
150 | SpriteMaterialRhiShader::SpriteMaterialRhiShader() |
151 | { |
152 | setShaderFileName(stage: VertexStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.vert.qsb" )); |
153 | setShaderFileName(stage: FragmentStage, QStringLiteral(":/qt-project.org/scenegraph/shaders_ng/sprite.frag.qsb" )); |
154 | } |
155 | |
156 | bool SpriteMaterialRhiShader::updateUniformData(RenderState &state, |
157 | QSGMaterial *newMaterial, QSGMaterial *oldMaterial) |
158 | { |
159 | #ifdef QT_NO_DEBUG |
160 | Q_UNUSED(oldMaterial); |
161 | #endif |
162 | Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type()); |
163 | QQuickSpriteMaterial *mat = static_cast<QQuickSpriteMaterial *>(newMaterial); |
164 | |
165 | bool changed = false; |
166 | QByteArray *buf = state.uniformData(); |
167 | Q_ASSERT(buf->size() >= 96); |
168 | |
169 | if (state.isMatrixDirty()) { |
170 | const QMatrix4x4 m = state.combinedMatrix(); |
171 | memcpy(dest: buf->data(), src: m.constData(), n: 64); |
172 | changed = true; |
173 | } |
174 | |
175 | float animPosAndData[7] = { mat->animX1, mat->animY1, mat->animX2, mat->animY2, |
176 | mat->animW, mat->animH, mat->animT }; |
177 | memcpy(dest: buf->data() + 64, src: animPosAndData, n: 28); |
178 | changed = true; |
179 | |
180 | if (state.isOpacityDirty()) { |
181 | const float opacity = state.opacity(); |
182 | memcpy(dest: buf->data() + 92, src: &opacity, n: 4); |
183 | changed = true; |
184 | } |
185 | |
186 | return changed; |
187 | } |
188 | |
189 | void SpriteMaterialRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture, |
190 | QSGMaterial *newMaterial, QSGMaterial *oldMaterial) |
191 | { |
192 | if (binding != 1) |
193 | return; |
194 | |
195 | #ifdef QT_NO_DEBUG |
196 | Q_UNUSED(oldMaterial); |
197 | #endif |
198 | Q_ASSERT(oldMaterial == nullptr || newMaterial->type() == oldMaterial->type()); |
199 | QQuickSpriteMaterial *mat = static_cast<QQuickSpriteMaterial *>(newMaterial); |
200 | |
201 | QSGTexture *t = mat->texture; |
202 | t->updateRhiTexture(rhi: state.rhi(), resourceUpdates: state.resourceUpdateBatch()); |
203 | *texture = t; |
204 | } |
205 | |
206 | QSGMaterialShader *QQuickSpriteMaterial::createShader() const |
207 | { |
208 | if (flags().testFlag(flag: RhiShaderWanted)) |
209 | return new SpriteMaterialRhiShader; |
210 | else |
211 | return new SpriteMaterialShader; |
212 | } |
213 | |
214 | static QSGGeometry::Attribute Sprite_Attributes[] = { |
215 | QSGGeometry::Attribute::create(pos: 0, tupleSize: 2, primitiveType: QSGGeometry::FloatType, isPosition: true), // pos |
216 | QSGGeometry::Attribute::create(pos: 1, tupleSize: 2, primitiveType: QSGGeometry::FloatType), // tex |
217 | }; |
218 | |
219 | static QSGGeometry::AttributeSet Sprite_AttributeSet = |
220 | { |
221 | .count: 2, // Attribute Count |
222 | .stride: (2+2) * sizeof(float), |
223 | .attributes: Sprite_Attributes |
224 | }; |
225 | |
226 | QSGDefaultSpriteNode::QSGDefaultSpriteNode() |
227 | : m_material(new QQuickSpriteMaterial) |
228 | , m_geometryDirty(true) |
229 | , m_sheetSize(QSize(64, 64)) |
230 | { |
231 | // Setup geometry data |
232 | m_geometry = new QSGGeometry(Sprite_AttributeSet, 4, 6); |
233 | m_geometry->setDrawingMode(QSGGeometry::DrawTriangles); |
234 | quint16 *indices = m_geometry->indexDataAsUShort(); |
235 | indices[0] = 0; |
236 | indices[1] = 1; |
237 | indices[2] = 2; |
238 | indices[3] = 1; |
239 | indices[4] = 3; |
240 | indices[5] = 2; |
241 | |
242 | setGeometry(m_geometry); |
243 | setMaterial(m_material); |
244 | setFlag(OwnsGeometry, true); |
245 | setFlag(OwnsMaterial, true); |
246 | } |
247 | |
248 | void QSGDefaultSpriteNode::setTexture(QSGTexture *texture) |
249 | { |
250 | m_material->texture = texture; |
251 | m_geometryDirty = true; |
252 | markDirty(bits: DirtyMaterial); |
253 | } |
254 | |
255 | void QSGDefaultSpriteNode::setTime(float time) |
256 | { |
257 | m_material->animT = time; |
258 | markDirty(bits: DirtyMaterial); |
259 | } |
260 | |
261 | void QSGDefaultSpriteNode::setSourceA(const QPoint &source) |
262 | { |
263 | if (m_sourceA != source) { |
264 | m_sourceA = source; |
265 | m_material->animX1 = static_cast<float>(source.x()) / m_sheetSize.width(); |
266 | m_material->animY1 = static_cast<float>(source.y()) / m_sheetSize.height(); |
267 | markDirty(bits: DirtyMaterial); |
268 | } |
269 | } |
270 | |
271 | void QSGDefaultSpriteNode::setSourceB(const QPoint &source) |
272 | { |
273 | if (m_sourceB != source) { |
274 | m_sourceB = source; |
275 | m_material->animX2 = static_cast<float>(source.x()) / m_sheetSize.width(); |
276 | m_material->animY2 = static_cast<float>(source.y()) / m_sheetSize.height(); |
277 | markDirty(bits: DirtyMaterial); |
278 | } |
279 | } |
280 | |
281 | void QSGDefaultSpriteNode::setSpriteSize(const QSize &size) |
282 | { |
283 | if (m_spriteSize != size) { |
284 | m_spriteSize = size; |
285 | m_material->animW = static_cast<float>(size.width()) / m_sheetSize.width(); |
286 | m_material->animH = static_cast<float>(size.height()) / m_sheetSize.height(); |
287 | markDirty(bits: DirtyMaterial); |
288 | } |
289 | |
290 | } |
291 | |
292 | void QSGDefaultSpriteNode::setSheetSize(const QSize &size) |
293 | { |
294 | if (m_sheetSize != size) { |
295 | m_sheetSize = size; |
296 | |
297 | // Update all dependent properties |
298 | m_material->animX1 = static_cast<float>(m_sourceA.x()) / m_sheetSize.width(); |
299 | m_material->animY1 = static_cast<float>(m_sourceA.y()) / m_sheetSize.height(); |
300 | m_material->animX2 = static_cast<float>(m_sourceB.x()) / m_sheetSize.width(); |
301 | m_material->animY2 = static_cast<float>(m_sourceB.y()) / m_sheetSize.height(); |
302 | m_material->animW = static_cast<float>(m_spriteSize.width()) / m_sheetSize.width(); |
303 | m_material->animH = static_cast<float>(m_spriteSize.height()) / m_sheetSize.height(); |
304 | markDirty(bits: DirtyMaterial); |
305 | } |
306 | } |
307 | |
308 | void QSGDefaultSpriteNode::setSize(const QSizeF &size) |
309 | { |
310 | if (m_size != size) { |
311 | m_size = size; |
312 | m_geometryDirty = true; |
313 | } |
314 | } |
315 | |
316 | void QSGDefaultSpriteNode::setFiltering(QSGTexture::Filtering filtering) |
317 | { |
318 | m_material->texture->setFiltering(filtering); |
319 | markDirty(bits: DirtyMaterial); |
320 | } |
321 | |
322 | void QSGDefaultSpriteNode::update() |
323 | { |
324 | if (m_geometryDirty) { |
325 | updateGeometry(); |
326 | m_geometryDirty = false; |
327 | } |
328 | } |
329 | |
330 | void QSGDefaultSpriteNode::updateGeometry() |
331 | { |
332 | if (!m_material->texture) |
333 | return; |
334 | |
335 | SpriteVertices *p = (SpriteVertices *) m_geometry->vertexData(); |
336 | |
337 | QRectF texRect = m_material->texture->normalizedTextureSubRect(); |
338 | |
339 | p->v1.tx = texRect.topLeft().x(); |
340 | p->v1.ty = texRect.topLeft().y(); |
341 | |
342 | p->v2.tx = texRect.topRight().x(); |
343 | p->v2.ty = texRect.topRight().y(); |
344 | |
345 | p->v3.tx = texRect.bottomLeft().x(); |
346 | p->v3.ty = texRect.bottomLeft().y(); |
347 | |
348 | p->v4.tx = texRect.bottomRight().x(); |
349 | p->v4.ty = texRect.bottomRight().y(); |
350 | |
351 | p->v1.x = 0; |
352 | p->v1.y = 0; |
353 | |
354 | p->v2.x = m_size.width(); |
355 | p->v2.y = 0; |
356 | |
357 | p->v3.x = 0; |
358 | p->v3.y = m_size.height(); |
359 | |
360 | p->v4.x = m_size.width(); |
361 | p->v4.y = m_size.height(); |
362 | markDirty(bits: DirtyGeometry); |
363 | } |
364 | |
365 | QT_END_NAMESPACE |
366 | |