Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
ShaderNodeRhi.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 fuddlesworth
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6#include <PhosphorRendering/phosphorrendering_export.h>
7
10
11#include <QColor>
12#include <QImage>
13#include <QMatrix4x4>
14#include <QPointF>
15#include <QPointer>
16#include <QSizeF>
17#include <QQuickItem>
18#include <QSGRenderNode>
19#include <QSGTextureProvider>
20#include <QString>
21#include <QStringList>
22#include <QVector>
23#include <QVector4D>
24
25#include <array>
26#include <atomic>
27#include <map>
28#include <memory>
29#include <mutex>
30
31#include <rhi/qrhi.h>
32
33namespace PhosphorRendering {
34
35// Forward declare constants used in member declarations.
36// `constexpr int` at namespace scope in a header is implicitly `inline` since
37// C++17 — no `static` needed (and matches the existing `constexpr int` style
38// used by kFirstFreeConsumerBinding / kMaxConsumerBinding below).
39constexpr int kMaxBufferPasses = 4;
40constexpr int kMaxUserTextures = 4;
41constexpr int kMaxCustomParams = 8;
42constexpr int kMaxCustomColors = 16;
43
44// ── Consumer binding range (for setExtraBinding) ────────────────────────
45// Library-managed bindings: 0 (UBO), 2-5 (multipass buffers), 6 (audio),
46// 7-10 (user textures), 11 (wallpaper), 12 (depth). Assigning any of those
47// via setExtraBinding() would duplicate SRB entries and is rejected at
48// runtime. Binding 1 is the one "free-in-the-gap" slot; 13..31 are free as
49// well.
50//
51// PlasmaZones uses binding 1 for its zone-labels texture by convention.
52// Other consumers that interoperate with PlasmaZones should pick from
53// 13..kMaxConsumerBinding to avoid a per-instance overwrite.
54constexpr int kFirstFreeConsumerBinding = 1;
59constexpr int kMaxConsumerBinding = 31;
60constexpr int kReservedBindingRangeStart = 2;
61constexpr int kReservedBindingRangeEnd = 12;
62
66constexpr int kUserTextureBaseBinding = 7;
67
69constexpr bool isConsumerBinding(int binding) noexcept
70{
71 return binding >= kFirstFreeConsumerBinding && binding <= kMaxConsumerBinding
72 && (binding < kReservedBindingRangeStart || binding > kReservedBindingRangeEnd);
73}
74
113class PHOSPHORRENDERING_EXPORT ShaderNodeRhi : public QSGRenderNode
114{
115public:
116 explicit ShaderNodeRhi(QQuickItem* item);
117 ~ShaderNodeRhi() override;
118
127
128 // QSGRenderNode
129 QSGRenderNode::StateFlags changedStates() const override;
130 QSGRenderNode::RenderingFlags flags() const override;
131 QRectF rect() const override;
132 void prepare() override;
133 void render(const RenderState* state) override;
134 void releaseResources() override;
135
136 // ── Uniform Extension ──────────────────────────────────────────────
137 void setUniformExtension(std::shared_ptr<PhosphorShaders::IUniformExtension> extension);
139 std::shared_ptr<PhosphorShaders::IUniformExtension> uniformExtension() const
140 {
141 return m_uniformExtension;
142 }
143
144 // ── Timing ─────────────────────────────────────────────────────────
145 void setTime(double time);
146 void setTimeDelta(float delta);
147 void setFrame(int frame);
148 void setResolution(float width, float height);
149 void setMousePosition(const QPointF& pos);
155 void setIsReversed(bool reverse);
156
157 // ── Custom Parameters (indexed API) ────────────────────────────────
158 void setCustomParams(int index, const QVector4D& params);
159 void setCustomColor(int index, const QColor& color);
160
161 // ── App Fields (consumer escape hatch in BaseUniforms) ─────────────
169 void setAppField0(int value);
170 void setAppField1(int value);
171
172 // ── Extra Bindings (consumer-managed texture bindings) ─────────────
187 bool setExtraBinding(int binding, QRhiTexture* texture, QRhiSampler* sampler);
189 bool removeExtraBinding(int binding);
190
191 // ── Textures ───────────────────────────────────────────────────────
192 void setAudioSpectrum(const QVector<float>& spectrum);
193 void setUserTexture(int slot, const QImage& image);
197 void setUserTextureWrap(int slot, const QString& wrap);
198 void setWallpaperTexture(const QImage& image);
199 void setUseWallpaper(bool use);
200 void setUseDepthBuffer(bool use);
201
216 void setSourceTextureProvider(QSGTextureProvider* provider);
217 QSGTextureProvider* sourceTextureProvider() const
218 {
219 return m_sourceTextureProvider.data();
220 }
221
222 // ── Multi-pass Buffers ─────────────────────────────────────────────
223 void setBufferShaderPath(const QString& path);
224 void setBufferShaderPaths(const QStringList& paths);
225 void setBufferFeedback(bool enable);
226 void setBufferScale(qreal scale);
227 void setBufferWrap(const QString& wrap);
228 void setBufferWraps(const QStringList& wraps);
229 void setBufferFilter(const QString& filter);
230 void setBufferFilters(const QStringList& filters);
231
232 // ── Shader Loading ─────────────────────────────────────────────────
233 bool loadVertexShader(const QString& path);
234 bool loadFragmentShader(const QString& path);
235 void setVertexShaderSource(const QString& source);
236 void setFragmentShaderSource(const QString& source);
237 bool isShaderReady() const;
238 QString shaderError() const;
241
242 // ── Include Paths ──────────────────────────────────────────────────
243 void setShaderIncludePaths(const QStringList& paths);
244
250 static QString normalizeWrapMode(const QString& wrap);
252 static QString normalizeFilterMode(const QString& filter);
253
257 static QRhiSampler::AddressMode wrapModeToRhiAddress(const QString& wrap);
258
259protected:
267 QRhi* safeRhi() const;
268
269private:
270 bool ensurePipeline();
271 bool ensureBufferPipeline();
272 bool ensureBufferTarget();
273 bool ensureDummyChannelResources(QRhi* rhi);
274 bool ensureBufferSampler(QRhi* rhi, int index);
275 void syncBaseUniforms();
276 void uploadDirtyTextures(QRhi* rhi, QRhiCommandBuffer* cb);
282 void uploadExtensionToUbo(QRhiResourceUpdateBatch* batch);
283 void releaseRhiResources();
284 void appendUserTextureBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
285 void appendWallpaperBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
286 void appendDepthBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
287 void appendExtraBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
288 void appendAudioBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
290 void appendUboAndExtraBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
293 void appendCommonTrailerBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
294 void resetAllBindingsAndPipelines();
295 void bakeBufferShaders();
296 QString loadAndExpandShader(const QString& path, QString* outError);
302 QString loadAndExpandShaderTracked(const QString& path, QStringList* outIncludedPaths, QString* outError);
303
304 QQuickItem* m_item = nullptr;
305 std::atomic<bool> m_itemValid{true};
306
316 // Lock-ordering invariant: MUST NOT block on the GUI thread while holding
317 // m_itemMutex (avoids deadlock during ~ShaderEffect → invalidateItem path).
318 mutable std::mutex m_itemMutex;
319
320 // ── Uniform Extension ──────────────────────────────────────────────
321 std::shared_ptr<PhosphorShaders::IUniformExtension> m_uniformExtension;
325 QByteArray m_extensionStaging;
326
327 // ── Shader Include Paths ───────────────────────────────────────────
328 QStringList m_shaderIncludePaths;
329
330 // ── RHI Core Resources ─────────────────────────────────────────────
331 std::unique_ptr<QRhiBuffer> m_vbo;
332 std::unique_ptr<QRhiBuffer> m_ubo;
333 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
334 std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
335 QShader m_vertexShader;
336 QShader m_fragmentShader;
337 QVector<quint32> m_renderPassFormat;
338
339 // ── Multi-pass: buffer pass(es) (optional). Up to 4 paths. ────────
340 QString m_bufferPath;
341 QStringList m_bufferPaths;
342 bool m_bufferFeedback = false;
343 qreal m_bufferScale = 1.0;
344 std::array<QString, kMaxBufferPasses> m_bufferWraps = {QStringLiteral("clamp"), QStringLiteral("clamp"),
345 QStringLiteral("clamp"), QStringLiteral("clamp")};
346 QString m_bufferWrapDefault = QStringLiteral("clamp");
347 std::array<QString, kMaxBufferPasses> m_bufferFilters = {QStringLiteral("linear"), QStringLiteral("linear"),
348 QStringLiteral("linear"), QStringLiteral("linear")};
349 QString m_bufferFilterDefault = QStringLiteral("linear");
350 QString m_bufferFragmentShaderSource;
351 QShader m_bufferFragmentShader;
352 bool m_bufferShaderReady = false;
353 bool m_bufferShaderDirty = true;
354 int m_bufferShaderRetries = 0;
355 std::unique_ptr<QRhiTexture> m_bufferTexture;
356 std::unique_ptr<QRhiRenderPassDescriptor> m_bufferRenderPassDescriptor;
357 std::unique_ptr<QRhiTextureRenderTarget> m_bufferRenderTarget;
358 std::array<std::unique_ptr<QRhiSampler>, kMaxBufferPasses> m_bufferSamplers;
359 std::unique_ptr<QRhiShaderResourceBindings> m_bufferSrb;
360 std::unique_ptr<QRhiGraphicsPipeline> m_bufferPipeline;
361 QVector<quint32> m_bufferRenderPassFormat;
362 // Ping-pong (bufferFeedback): second texture/RT/SRB for buffer pass; image pass has two SRBs
363 std::unique_ptr<QRhiTexture> m_bufferTextureB;
364 std::unique_ptr<QRhiRenderPassDescriptor> m_bufferRenderPassDescriptorB;
365 std::unique_ptr<QRhiTextureRenderTarget> m_bufferRenderTargetB;
366 std::unique_ptr<QRhiShaderResourceBindings> m_bufferSrbB;
367 std::unique_ptr<QRhiShaderResourceBindings> m_srbB; // image pass SRB with binding 2 = texture B
368 bool m_bufferFeedbackCleared = false;
369
370 // Multi-buffer mode (2-4 passes): per-pass resources
371 std::array<std::unique_ptr<QRhiTexture>, kMaxBufferPasses> m_multiBufferTextures = {};
372 std::array<std::unique_ptr<QRhiTextureRenderTarget>, kMaxBufferPasses> m_multiBufferRenderTargets = {};
373 std::array<std::unique_ptr<QRhiRenderPassDescriptor>, kMaxBufferPasses> m_multiBufferRenderPassDescriptors = {};
374 std::array<std::unique_ptr<QRhiGraphicsPipeline>, kMaxBufferPasses> m_multiBufferPipelines = {};
375 std::array<std::unique_ptr<QRhiShaderResourceBindings>, kMaxBufferPasses> m_multiBufferSrbs = {};
376 std::array<QShader, kMaxBufferPasses> m_multiBufferFragmentShaders = {};
377 std::array<QString, kMaxBufferPasses> m_multiBufferFragmentShaderSources = {};
378 bool m_multiBufferShadersReady = false;
379 bool m_multiBufferShaderDirty = true;
380 int m_multiBufferShaderRetries = 0;
381 // Dummy 1x1 texture for the multipass channel-0 buffer slot (SRB
382 // binding 2, GLSL `iChannel0`) when multipass is configured but the
383 // backing buffer hasn't been created yet. Distinct from the user-
384 // texture slot 0 (SRB binding 7, GLSL `uTexture0`) — the iChannel0
385 // name here refers to the buffer-channel binding, not the renamed
386 // user-texture.
387 std::unique_ptr<QRhiTexture> m_dummyChannelTexture;
388 std::unique_ptr<QRhiSampler> m_dummyChannelSampler;
389 bool m_dummyChannelTextureNeedsUpload = false;
390
391 // ── Shader Sources ─────────────────────────────────────────────────
392 QString m_vertexShaderSource;
393 QString m_fragmentShaderSource;
394 QString m_vertexPath;
395 QString m_fragmentPath;
396 qint64 m_vertexMtime = 0;
397 qint64 m_fragmentMtime = 0;
406 QStringList m_vertexIncludedPaths;
407 QStringList m_fragmentIncludedPaths;
408 QString m_shaderError;
409 bool m_initialized = false;
410 bool m_vboUploaded = false;
411 bool m_shaderReady = false;
412 bool m_shaderDirty = true;
413 bool m_uniformsDirty = true;
414 bool m_timeDirty = true;
415 bool m_timeHiDirty = true;
416 bool m_sceneDataDirty = true;
417 bool m_appFieldsDirty = false;
418 bool m_didFullUploadOnce = false;
422 qint64 m_lastDateRefreshMs = 0;
423
424 // ── Base Uniforms ──────────────────────────────────────────────────
425 PhosphorShaders::BaseUniforms m_baseUniforms = {};
426
427 // Full-precision elapsed seconds (double). Split into iTime (wrapped lo) +
428 // iTimeHi (wrap offset) at upload.
429 double m_time = 0.0;
430 float m_timeDelta = 0.0f;
431 int m_frame = 0;
432 bool m_isReversed = false;
433 float m_timeHi = 0.0f; // Cached iTimeHi for wrap-offset change detection
434 float m_width = 0.0f;
435 float m_height = 0.0f;
442 std::atomic<float> m_cachedWidth{0.0f};
443 std::atomic<float> m_cachedHeight{0.0f};
444 QPointF m_mousePosition;
445
446 // ── Custom Parameters (indexed) ────────────────────────────────────
447 std::array<QVector4D, kMaxCustomParams> m_customParams;
448 std::array<QColor, kMaxCustomColors> m_customColors;
449
450 // ── Extra Bindings (consumer-managed) ──────────────────────────────
451 struct ExtraBinding
452 {
453 QRhiTexture* texture = nullptr;
454 QRhiSampler* sampler = nullptr;
455 };
456 // std::map (ordered) so SRB construction produces a deterministic binding
457 // order regardless of insertion/erasure history. An unordered_map can
458 // produce different iteration orders after erasures, which Qt RHI
459 // backends may hash into different pipeline layout signatures.
460 std::map<int, ExtraBinding> m_extraBindings;
461 bool m_extraBindingsDirty = false;
462
463 // ── 1x1 Transparent Fallback ───────────────────────────────────────
464 QImage m_transparentFallbackImage;
465
466 // ── Audio spectrum texture (binding 6) ─────────────────────────────
467 QVector<float> m_audioSpectrum;
468 std::unique_ptr<QRhiTexture> m_audioSpectrumTexture;
469 std::unique_ptr<QRhiSampler> m_audioSpectrumSampler;
470 bool m_audioSpectrumDirty = false;
471
472 // ── User texture slots (bindings 7-10) ─────────────────────────────
473 std::array<QImage, kMaxUserTextures> m_userTextureImages;
474 std::array<std::unique_ptr<QRhiTexture>, kMaxUserTextures> m_userTextures;
475 std::array<std::unique_ptr<QRhiSampler>, kMaxUserTextures> m_userTextureSamplers;
476 std::array<QString, kMaxUserTextures> m_userTextureWraps;
477 std::array<bool, kMaxUserTextures> m_userTextureDirty = {};
478
479 // ── Source texture override (slot 0 / binding 7) ───────────────────
480 // Texture-provider source — typically a `QQuickItem::textureProvider()`
481 // for a layer-enabled item. When non-null this supersedes
482 // m_userTextures[0] in the SRB build, so the shader's uTexture0
483 // samples a live QML render instead of a static QImage upload.
484 // m_sourceSampler is owned here (not a user-texture sampler) so we
485 // can keep slot 0's user-texture sampler available for callers that
486 // mix the two paths. m_lastSourceRhiTexture caches the QRhiTexture*
487 // we last bound; when the provider's underlying texture changes
488 // (FBO recreation on resize / device-loss) we drop the SRB so the
489 // next rebuild picks up the new pointer.
490 QPointer<QSGTextureProvider> m_sourceTextureProvider;
491 std::unique_ptr<QRhiSampler> m_sourceSampler;
492 QRhiTexture* m_lastSourceRhiTexture = nullptr;
497 bool m_sourceSamplerFailed = false;
501 bool m_warnedForeignRhi = false;
509 std::unique_ptr<QRhiTexture> m_transparentFallbackTexture;
510 bool m_transparentFallbackTextureNeedsUpload = false;
511
512 // ── Depth buffer (binding 12) ──────────────────────────────────────
513 bool m_useDepthBuffer = false;
514 bool m_depthMultiBufferWarned = false;
515 std::unique_ptr<QRhiTexture> m_depthTexture;
516 std::unique_ptr<QRhiSampler> m_depthSampler;
517
518 // ── Desktop wallpaper texture (binding 11) ─────────────────────────
519 bool m_useWallpaper = false;
520 QImage m_wallpaperImage;
521 std::unique_ptr<QRhiTexture> m_wallpaperTexture;
522 std::unique_ptr<QRhiSampler> m_wallpaperSampler;
523 bool m_wallpaperDirty = false;
524};
525
528{
529 bool success = false;
531};
532
541PHOSPHORRENDERING_EXPORT WarmShaderBakeResult warmShaderBakeCacheForPaths(const QString& vertexPath,
542 const QString& fragmentPath,
543 const QStringList& includePaths = {});
544
545} // namespace PhosphorRendering
QSGRenderNode for fullscreen-quad shader rendering via Qt RHI (Vulkan / OpenGL)
Definition ShaderNodeRhi.h:114
bool removeExtraBinding(int binding)
void setBufferScale(qreal scale)
void setUniformExtension(std::shared_ptr< PhosphorShaders::IUniformExtension > extension)
void setIsReversed(bool reverse)
Direction signal for asymmetric leg rendering.
void setVertexShaderSource(const QString &source)
void setShaderIncludePaths(const QStringList &paths)
QRectF rect() const override
QSGRenderNode::RenderingFlags flags() const override
void setBufferWrap(const QString &wrap)
void setCustomParams(int index, const QVector4D &params)
void setUserTexture(int slot, const QImage &image)
void invalidateItem()
Notify the render node that its owning item is being destroyed.
void setBufferFilter(const QString &filter)
void setAppField0(int value)
Write the consumer's two int slots inside BaseUniforms (offsets 88, 92).
void setBufferShaderPaths(const QStringList &paths)
void setAudioSpectrum(const QVector< float > &spectrum)
void setBufferWraps(const QStringList &wraps)
bool loadFragmentShader(const QString &path)
void setUserTextureWrap(int slot, const QString &wrap)
Set per-slot sampler address mode.
void setWallpaperTexture(const QImage &image)
QRhi * safeRhi() const
Thread-safe QRhi accessor.
void setMousePosition(const QPointF &pos)
void setResolution(float width, float height)
std::shared_ptr< PhosphorShaders::IUniformExtension > uniformExtension() const
Access the currently-installed uniform extension (may be nullptr).
Definition ShaderNodeRhi.h:139
void setFragmentShaderSource(const QString &source)
void setSourceTextureProvider(QSGTextureProvider *provider)
Live texture-provider override for user-texture slot 0 (SRB binding 7 / uTexture0).
void setBufferShaderPath(const QString &path)
QSGTextureProvider * sourceTextureProvider() const
Definition ShaderNodeRhi.h:217
void render(const RenderState *state) override
static QString normalizeFilterMode(const QString &filter)
Normalize filter mode string to "nearest", "linear", or "mipmap".
void setCustomColor(int index, const QColor &color)
QSGRenderNode::StateFlags changedStates() const override
static QString normalizeWrapMode(const QString &wrap)
Normalize wrap mode string to "clamp", "repeat", or "mirror" (static helper, safe to call from any th...
bool loadVertexShader(const QString &path)
bool setExtraBinding(int binding, QRhiTexture *texture, QRhiSampler *sampler)
Bind a consumer-owned texture/sampler at the given binding number.
void setBufferFeedback(bool enable)
void setBufferFilters(const QStringList &filters)
static QRhiSampler::AddressMode wrapModeToRhiAddress(const QString &wrap)
Map a normalized wrap-mode string to QRhiSampler::AddressMode.
Definition ShaderCompiler.h:15
constexpr int kMaxBufferPasses
Definition ShaderNodeRhi.h:39
constexpr int kReservedBindingRangeEnd
Last library-managed binding.
Definition ShaderNodeRhi.h:61
PHOSPHORRENDERING_EXPORT WarmShaderBakeResult warmShaderBakeCacheForPaths(const QString &vertexPath, const QString &fragmentPath, const QStringList &includePaths={})
Pre-load cache warming: load, bake, and insert shaders for the given paths into the shared bake cache...
constexpr int kMaxCustomColors
Definition ShaderNodeRhi.h:42
constexpr int kFirstFreeConsumerBinding
First slot usable via setExtraBinding()
Definition ShaderNodeRhi.h:54
constexpr int kReservedBindingRangeStart
First library-managed binding above 0.
Definition ShaderNodeRhi.h:60
constexpr bool isConsumerBinding(int binding) noexcept
Definition ShaderNodeRhi.h:69
constexpr int kMaxConsumerBinding
Highest portable SRB binding.
Definition ShaderNodeRhi.h:59
constexpr int kMaxUserTextures
Definition ShaderNodeRhi.h:40
constexpr int kUserTextureBaseBinding
First SRB binding for the user-texture slots (slot 0 → binding 7).
Definition ShaderNodeRhi.h:66
constexpr int kMaxCustomParams
Definition ShaderNodeRhi.h:41
Result of warmShaderBakeCacheForPaths for reporting to UI.
Definition ShaderNodeRhi.h:528
QString errorMessage
Definition ShaderNodeRhi.h:530
bool success
Definition ShaderNodeRhi.h:529
GPU uniform buffer layout following std140 rules (base region).
Definition BaseUniforms.h:37