1/*
2 * SPDX-FileCopyrightText: 2025 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "shadernode.h"
8
9#include <QSGTextureProvider>
10#include <QVariant>
11
12#include "shadermaterial.h"
13#include "texturecache.h"
14
15struct VertexLayout {
16 using RectPropertyFunction = qreal (QRectF::*)() const;
17
18 RectPropertyFunction x;
19 RectPropertyFunction y;
20};
21
22static const std::array<VertexLayout, 4> Vertices = {
23 VertexLayout{.x = &QRectF::left, .y = &QRectF::top},
24 VertexLayout{.x = &QRectF::left, .y = &QRectF::bottom},
25 VertexLayout{.x = &QRectF::right, .y = &QRectF::top},
26 VertexLayout{.x = &QRectF::right, .y = &QRectF::bottom},
27};
28
29ShaderNode::ShaderNode()
30 : m_rect(QRectF{0.0, 0.0, 1.0, 1.0})
31 , m_uvs(16, QRectF{0.0, 0.0, 1.0, 1.0})
32{
33 setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial | QSGNode::UsePreprocess);
34}
35
36ShaderNode::~ShaderNode() noexcept
37{
38 for (auto texture : std::as_const(t&: m_textures)) {
39 if (texture.provider) {
40 texture.provider->disconnect(texture.providerConnection);
41 }
42 }
43
44 delete[] m_attributeSet->attributes;
45 delete m_attributeSet;
46}
47
48void ShaderNode::preprocess()
49{
50 for (const auto &info : std::as_const(t&: m_textures)) {
51 if (info.provider) {
52 preprocessTexture(texture: info);
53 }
54 }
55}
56
57QRectF ShaderNode::rect() const
58{
59 return m_rect;
60}
61
62void ShaderNode::setRect(const QRectF &newRect)
63{
64 if (newRect == m_rect) {
65 return;
66 }
67
68 m_rect = newRect;
69 m_geometryUpdateNeeded = true;
70}
71
72QRectF ShaderNode::uvs(TextureChannel channel) const
73{
74 Q_ASSERT(channel < m_textureChannels);
75 return m_uvs[channel];
76}
77
78void ShaderNode::setUVs(TextureChannel channel, const QRectF &newUvs)
79{
80 Q_ASSERT(channel < m_textureChannels);
81
82 if (newUvs == m_uvs[channel]) {
83 return;
84 }
85
86 m_uvs[channel] = newUvs;
87 m_geometryUpdateNeeded = true;
88}
89
90QSGMaterialType *ShaderNode::materialVariant() const
91{
92 return m_materialVariant;
93}
94
95void ShaderNode::setMaterialVariant(QSGMaterialType *variant)
96{
97 if (variant == m_materialVariant) {
98 return;
99 }
100
101 m_materialVariant = variant;
102
103 auto newMaterial = createMaterialVariant(variant: m_materialVariant);
104 if (newMaterial) {
105 m_shaderMaterial = dynamic_cast<ShaderMaterial *>(newMaterial);
106 setMaterial(newMaterial);
107 markDirty(bits: QSGNode::DirtyMaterial);
108 }
109}
110
111void ShaderNode::setShader(const QString &shader)
112{
113 setMaterialVariant(ShaderMaterial::typeForName(name: shader));
114}
115
116void ShaderNode::setUniformBufferSize(qsizetype size)
117{
118 if (!m_shaderMaterial) {
119 return;
120 }
121
122 m_shaderMaterial->setUniformBufferSize(size);
123}
124
125std::span<char> ShaderNode::uniformData()
126{
127 if (!m_shaderMaterial) {
128 return std::span<char>{};
129 }
130
131 return m_shaderMaterial->uniformData();
132}
133
134void ShaderNode::setTextureChannels(unsigned char count)
135{
136 if (count == m_textureChannels) {
137 return;
138 }
139
140 m_textureChannels = std::clamp(val: count, lo: uint8_t(1), hi: uint8_t(16));
141
142 if (geometry()) {
143 setGeometry(nullptr);
144 delete[] m_attributeSet->attributes;
145 delete m_attributeSet;
146 }
147
148 while (m_textures.size() > count) {
149 m_textures.removeLast();
150 }
151
152 m_geometryUpdateNeeded = true;
153}
154
155void ShaderNode::setTexture(TextureChannel channel, const QImage &image, QQuickWindow *window, QQuickWindow::CreateTextureOptions options)
156{
157 if (!m_shaderMaterial) {
158 return;
159 }
160
161 auto texture = TextureCache::loadTexture(window, image, options);
162 if (!texture) {
163 return;
164 }
165
166 auto info = TextureInfo{
167 .channel = channel,
168 .options = options,
169 .texture = texture,
170 .provider = nullptr,
171 .providerConnection = {},
172 };
173
174 auto itr = std::find_if(first: m_textures.begin(), last: m_textures.end(), pred: [channel](auto info) {
175 return info.channel == channel;
176 });
177 if (itr != m_textures.end()) {
178 *itr = info;
179 } else {
180 m_textures.append(t: info);
181 }
182
183 setUVs(channel, newUvs: texture->normalizedTextureSubRect());
184
185 m_shaderMaterial->setTexture(binding: channel + 1, texture: texture.get());
186 markDirty(bits: QSGNode::DirtyMaterial);
187}
188
189void ShaderNode::setTexture(TextureChannel channel, QSGTextureProvider *provider, QQuickWindow::CreateTextureOptions options)
190{
191 if (!m_shaderMaterial) {
192 return;
193 }
194
195 auto connection = QObject::connect(sender: provider, signal: &QSGTextureProvider::textureChanged, slot: [this]() {
196 markDirty(bits: QSGNode::DirtyMaterial);
197 });
198
199 auto info = TextureInfo{
200 .channel = channel,
201 .options = options,
202 .texture = nullptr,
203 .provider = provider,
204 .providerConnection = connection,
205 };
206
207 auto itr = std::find_if(first: m_textures.begin(), last: m_textures.end(), pred: [channel](auto info) {
208 return info.channel == channel;
209 });
210 if (itr != m_textures.end()) {
211 if (itr->provider) {
212 itr->provider->disconnect(itr->providerConnection);
213 }
214 *itr = info;
215 } else {
216 m_textures.append(t: info);
217 }
218}
219
220void ShaderNode::update()
221{
222 if (m_geometryUpdateNeeded) {
223 const auto attributeCount = 1 + m_textureChannels;
224
225 if (!geometry()) {
226 QSGGeometry::Attribute *attributes = new QSGGeometry::Attribute[attributeCount];
227 attributes[0] = QSGGeometry::Attribute::createWithAttributeType(pos: 0, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::PositionAttribute);
228
229 for (int i = 0; i < m_textureChannels; ++i) {
230 attributes[i + 1] = QSGGeometry::Attribute::createWithAttributeType(pos: i + 1, tupleSize: 2, primitiveType: QSGGeometry::FloatType, attributeType: QSGGeometry::TexCoordAttribute);
231 }
232
233 m_attributeSet =
234 new QSGGeometry::AttributeSet{.count = attributeCount, .stride = int(sizeof(float)) * 2 * attributeCount, .attributes = attributes};
235
236 setGeometry(new QSGGeometry{*m_attributeSet, Vertices.size()});
237 }
238
239 auto vertices = static_cast<float *>(geometry()->vertexData());
240
241 auto index = 0;
242 for (auto layout : Vertices) {
243 vertices[index++] = (m_rect.*layout.x)();
244 vertices[index++] = (m_rect.*layout.y)();
245
246 for (int channel = 0; channel < m_textureChannels; ++channel) {
247 auto uv = uvs(channel);
248 vertices[index++] = (uv.*layout.x)();
249 vertices[index++] = (uv.*layout.y)();
250 }
251 }
252
253 markDirty(bits: QSGNode::DirtyGeometry);
254 m_geometryUpdateNeeded = false;
255 }
256}
257
258QSGMaterial *ShaderNode::createMaterialVariant(QSGMaterialType *variant)
259{
260 return new ShaderMaterial(variant);
261}
262
263void ShaderNode::preprocessTexture(const TextureInfo &info)
264{
265 auto provider = info.provider;
266 if (!provider || !provider->texture() || !m_shaderMaterial) {
267 return;
268 }
269
270 if (provider->texture()->isAtlasTexture() && !info.options.testFlag(flag: QQuickWindow::TextureCanUseAtlas)) {
271 m_shaderMaterial->setTexture(binding: info.channel + 1, texture: provider->texture()->removedFromAtlas());
272 } else {
273 m_shaderMaterial->setTexture(binding: info.channel + 1, texture: provider->texture());
274 }
275 if (QSGDynamicTexture *dynamic_texture = qobject_cast<QSGDynamicTexture *>(object: provider->texture())) {
276 dynamic_texture->updateTexture();
277 }
278}
279

source code of kirigami/src/primitives/scenegraph/shadernode.cpp