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
11
12#include <QColor>
13#include <QImage>
14#include <QMatrix4x4>
15#include <QPointF>
16#include <QPointer>
17#include <QSizeF>
18#include <QQuickItem>
19#include <QSGRenderNode>
20#include <QSGTextureProvider>
21#include <QString>
22#include <QStringList>
23#include <QVector>
24#include <QVector4D>
25
26#include <array>
27#include <atomic>
28#include <map>
29#include <memory>
30#include <mutex>
31
32#include <rhi/qrhi.h>
33
34namespace PhosphorRendering {
35
36// Forward declare constants used in member declarations.
37// `constexpr int` at namespace scope in a header is implicitly `inline` since
38// C++17 — no `static` needed (and matches the existing `constexpr int` style
39// used by kFirstFreeConsumerBinding / kMaxConsumerBinding below).
40constexpr int kMaxBufferPasses = 4;
41constexpr int kMaxUserTextures = 4;
42constexpr int kMaxCustomParams = 8;
43constexpr int kMaxCustomColors = 16;
44
45// ── Consumer binding range (for setExtraBinding) ────────────────────────
46// Library-managed bindings: 0 (UBO), 2-5 (multipass buffers), 6 (audio),
47// 7-10 (user textures), 11 (wallpaper), 12 (depth). Assigning any of those
48// via setExtraBinding() would duplicate SRB entries and is rejected at
49// runtime. Binding 1 is the one "free-in-the-gap" slot; 13..31 are free as
50// well.
51//
52// Phosphor uses binding 1 for its zone-labels texture by convention.
53// Other consumers that interoperate with Phosphor should pick from
54// 13..kMaxConsumerBinding to avoid a per-instance overwrite.
55constexpr int kFirstFreeConsumerBinding = 1;
60constexpr int kMaxConsumerBinding = 31;
61constexpr int kReservedBindingRangeStart = 2;
62constexpr int kReservedBindingRangeEnd = 12;
63
67constexpr int kUserTextureBaseBinding = 7;
68
70constexpr bool isConsumerBinding(int binding) noexcept
71{
72 return binding >= kFirstFreeConsumerBinding && binding <= kMaxConsumerBinding
73 && (binding < kReservedBindingRangeStart || binding > kReservedBindingRangeEnd);
74}
75
114class PHOSPHORRENDERING_EXPORT ShaderNodeRhi : public QSGRenderNode
115{
116public:
117 explicit ShaderNodeRhi(QQuickItem* item);
118 ~ShaderNodeRhi() override;
119
128
129 // QSGRenderNode
130 QSGRenderNode::StateFlags changedStates() const override;
131 QSGRenderNode::RenderingFlags flags() const override;
132 QRectF rect() const override;
133 void prepare() override;
134 void render(const RenderState* state) override;
135 void releaseResources() override;
136
137 // ── Uniform Extension ──────────────────────────────────────────────
138 void setUniformExtension(std::shared_ptr<PhosphorShaders::IUniformExtension> extension);
140 std::shared_ptr<PhosphorShaders::IUniformExtension> uniformExtension() const
141 {
142 return m_uniformExtension;
143 }
144
145 // ── Timing ─────────────────────────────────────────────────────────
146 void setTime(double time);
147 void setTimeDelta(float delta);
148 void setFrame(int frame);
149 void setResolution(float width, float height);
150 void setMousePosition(const QPointF& pos);
156 void setIsReversed(bool reverse);
157
158 // ── Custom Parameters (indexed API) ────────────────────────────────
159 void setCustomParams(int index, const QVector4D& params);
160 void setCustomColor(int index, const QColor& color);
161
162 // ── App Fields (consumer escape hatch in BaseUniforms) ─────────────
170 void setAppField0(int value);
171 void setAppField1(int value);
172
173 // ── Extra Bindings (consumer-managed texture bindings) ─────────────
188 bool setExtraBinding(int binding, QRhiTexture* texture, QRhiSampler* sampler);
190 bool removeExtraBinding(int binding);
191
192 // ── Textures ───────────────────────────────────────────────────────
193 void setAudioSpectrum(const QVector<float>& spectrum);
194 void setUserTexture(int slot, const QImage& image);
198 void setUserTextureWrap(int slot, const QString& wrap);
199 void setWallpaperTexture(const QImage& image);
200 void setUseWallpaper(bool use);
201 void setUseDepthBuffer(bool use);
202
217 void setSourceTextureProvider(QSGTextureProvider* provider);
218 QSGTextureProvider* sourceTextureProvider() const
219 {
220 return m_sourceTextureProvider.data();
221 }
222
223 // ── Multi-pass Buffers ─────────────────────────────────────────────
224 void setBufferShaderPath(const QString& path);
225 void setBufferShaderPaths(const QStringList& paths);
226 void setBufferFeedback(bool enable);
227 void setBufferScale(qreal scale);
228 void setBufferWrap(const QString& wrap);
229 void setBufferWraps(const QStringList& wraps);
230 void setBufferFilter(const QString& filter);
231 void setBufferFilters(const QStringList& filters);
232
233 // ── Shader Loading ─────────────────────────────────────────────────
234 bool loadVertexShader(const QString& path);
235 bool loadFragmentShader(const QString& path);
236 void setVertexShaderSource(const QString& source);
237 void setFragmentShaderSource(const QString& source);
238 bool isShaderReady() const;
239 QString shaderError() const;
242
243 // ── Include Paths ──────────────────────────────────────────────────
244 void setShaderIncludePaths(const QStringList& paths);
245
246 // ── Parameter preamble (T1.1: generated `#define p_<id> ...`) ─────
256 void setParamPreamble(const QString& preamble);
257
258 // ── Entry-point scaffold (T1.4: harness-generated `main()`) ────────
269 void setEntryScaffold(const QString& prologue, const QList<PhosphorShaders::EntryCandidate>& candidates);
270
276 static QString normalizeWrapMode(const QString& wrap);
278 static QString normalizeFilterMode(const QString& filter);
279
283 static QRhiSampler::AddressMode wrapModeToRhiAddress(const QString& wrap);
284
285protected:
293 QRhi* safeRhi() const;
294
295private:
296 bool ensurePipeline();
297 bool ensureBufferPipeline();
298 bool ensureBufferTarget();
299 bool ensureDummyChannelResources(QRhi* rhi);
300 bool ensureBufferSampler(QRhi* rhi, int index);
301 void syncBaseUniforms();
302 void uploadDirtyTextures(QRhi* rhi, QRhiCommandBuffer* cb);
308 void uploadExtensionToUbo(QRhiResourceUpdateBatch* batch);
309 void releaseRhiResources();
310 void appendUserTextureBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
311 void appendWallpaperBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
312 void appendDepthBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
313 void appendExtraBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
314 void appendAudioBinding(QVector<QRhiShaderResourceBinding>& bindings) const;
316 void appendUboAndExtraBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
319 void appendCommonTrailerBindings(QVector<QRhiShaderResourceBinding>& bindings) const;
320 void resetAllBindingsAndPipelines();
321 void bakeBufferShaders();
322 QString loadAndExpandShader(const QString& path, QString* outError);
328 QString loadAndExpandShaderTracked(const QString& path, QStringList* outIncludedPaths, QString* outError);
329
330 QQuickItem* m_item = nullptr;
331 std::atomic<bool> m_itemValid{true};
332
342 // Lock-ordering invariant: MUST NOT block on the GUI thread while holding
343 // m_itemMutex (avoids deadlock during ~ShaderEffect → invalidateItem path).
344 mutable std::mutex m_itemMutex;
345
346 // ── Uniform Extension ──────────────────────────────────────────────
347 std::shared_ptr<PhosphorShaders::IUniformExtension> m_uniformExtension;
351 QByteArray m_extensionStaging;
352
353 // ── Shader Include Paths ───────────────────────────────────────────
354 QStringList m_shaderIncludePaths;
355
356 // ── Parameter preamble (generated `#define p_<id> ...`) ───────────
359 QString m_paramPreamble;
360
361 // ── Entry-point scaffold (T1.4) ────────────────────────────────────
365 QString m_entryPrologue;
366 QList<PhosphorShaders::EntryCandidate> m_entryCandidates;
367
368 // ── RHI Core Resources ─────────────────────────────────────────────
369 std::unique_ptr<QRhiBuffer> m_vbo;
370 std::unique_ptr<QRhiBuffer> m_ubo;
371 std::unique_ptr<QRhiShaderResourceBindings> m_srb;
372 std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
373 QShader m_vertexShader;
374 QShader m_fragmentShader;
375 QVector<quint32> m_renderPassFormat;
376
377 // ── Multi-pass: buffer pass(es) (optional). Up to 4 paths. ────────
378 QString m_bufferPath;
379 QStringList m_bufferPaths;
380 bool m_bufferFeedback = false;
381 qreal m_bufferScale = 1.0;
382 std::array<QString, kMaxBufferPasses> m_bufferWraps = {QStringLiteral("clamp"), QStringLiteral("clamp"),
383 QStringLiteral("clamp"), QStringLiteral("clamp")};
384 QString m_bufferWrapDefault = QStringLiteral("clamp");
385 std::array<QString, kMaxBufferPasses> m_bufferFilters = {QStringLiteral("linear"), QStringLiteral("linear"),
386 QStringLiteral("linear"), QStringLiteral("linear")};
387 QString m_bufferFilterDefault = QStringLiteral("linear");
388 QString m_bufferFragmentShaderSource;
389 QShader m_bufferFragmentShader;
390 bool m_bufferShaderReady = false;
391 bool m_bufferShaderDirty = true;
392 int m_bufferShaderRetries = 0;
393 std::unique_ptr<QRhiTexture> m_bufferTexture;
394 std::unique_ptr<QRhiRenderPassDescriptor> m_bufferRenderPassDescriptor;
395 std::unique_ptr<QRhiTextureRenderTarget> m_bufferRenderTarget;
396 std::array<std::unique_ptr<QRhiSampler>, kMaxBufferPasses> m_bufferSamplers;
397 std::unique_ptr<QRhiShaderResourceBindings> m_bufferSrb;
398 std::unique_ptr<QRhiGraphicsPipeline> m_bufferPipeline;
399 QVector<quint32> m_bufferRenderPassFormat;
400 // Ping-pong (bufferFeedback): second texture/RT/SRB for buffer pass; image pass has two SRBs
401 std::unique_ptr<QRhiTexture> m_bufferTextureB;
402 std::unique_ptr<QRhiRenderPassDescriptor> m_bufferRenderPassDescriptorB;
403 std::unique_ptr<QRhiTextureRenderTarget> m_bufferRenderTargetB;
404 std::unique_ptr<QRhiShaderResourceBindings> m_bufferSrbB;
405 std::unique_ptr<QRhiShaderResourceBindings> m_srbB; // image pass SRB with binding 2 = texture B
406 bool m_bufferFeedbackCleared = false;
407
408 // Multi-buffer mode (2-4 passes): per-pass resources
409 std::array<std::unique_ptr<QRhiTexture>, kMaxBufferPasses> m_multiBufferTextures = {};
410 std::array<std::unique_ptr<QRhiTextureRenderTarget>, kMaxBufferPasses> m_multiBufferRenderTargets = {};
411 std::array<std::unique_ptr<QRhiRenderPassDescriptor>, kMaxBufferPasses> m_multiBufferRenderPassDescriptors = {};
412 std::array<std::unique_ptr<QRhiGraphicsPipeline>, kMaxBufferPasses> m_multiBufferPipelines = {};
413 std::array<std::unique_ptr<QRhiShaderResourceBindings>, kMaxBufferPasses> m_multiBufferSrbs = {};
414 std::array<QShader, kMaxBufferPasses> m_multiBufferFragmentShaders = {};
415 std::array<QString, kMaxBufferPasses> m_multiBufferFragmentShaderSources = {};
416 bool m_multiBufferShadersReady = false;
417 bool m_multiBufferShaderDirty = true;
418 int m_multiBufferShaderRetries = 0;
419 // Dummy 1x1 texture for the multipass channel-0 buffer slot (SRB
420 // binding 2, GLSL `iChannel0`) when multipass is configured but the
421 // backing buffer hasn't been created yet. Distinct from the user-
422 // texture slot 0 (SRB binding 7, GLSL `uTexture0`) — the iChannel0
423 // name here refers to the buffer-channel binding, not the renamed
424 // user-texture.
425 std::unique_ptr<QRhiTexture> m_dummyChannelTexture;
426 std::unique_ptr<QRhiSampler> m_dummyChannelSampler;
427 bool m_dummyChannelTextureNeedsUpload = false;
428
429 // ── Shader Sources ─────────────────────────────────────────────────
430 QString m_vertexShaderSource;
431 QString m_fragmentShaderSource;
432 QString m_vertexPath;
433 QString m_fragmentPath;
434 qint64 m_vertexMtime = 0;
435 qint64 m_fragmentMtime = 0;
444 QStringList m_vertexIncludedPaths;
445 QStringList m_fragmentIncludedPaths;
446 QString m_shaderError;
447 bool m_initialized = false;
448 bool m_vboUploaded = false;
449 bool m_shaderReady = false;
450 bool m_shaderDirty = true;
451 bool m_uniformsDirty = true;
452 bool m_timeDirty = true;
453 bool m_timeHiDirty = true;
454 bool m_sceneDataDirty = true;
455 bool m_appFieldsDirty = false;
456 bool m_didFullUploadOnce = false;
460 qint64 m_lastDateRefreshMs = 0;
461
462 // ── Base Uniforms ──────────────────────────────────────────────────
463 PhosphorShaders::BaseUniforms m_baseUniforms = {};
464
465 // Full-precision elapsed seconds (double). Split into iTime (wrapped lo) +
466 // iTimeHi (wrap offset) at upload.
467 double m_time = 0.0;
468 float m_timeDelta = 0.0f;
469 int m_frame = 0;
470 bool m_isReversed = false;
471 float m_timeHi = 0.0f; // Cached iTimeHi for wrap-offset change detection
472 float m_width = 0.0f;
473 float m_height = 0.0f;
480 std::atomic<float> m_cachedWidth{0.0f};
481 std::atomic<float> m_cachedHeight{0.0f};
482 QPointF m_mousePosition;
483
484 // ── Custom Parameters (indexed) ────────────────────────────────────
485 std::array<QVector4D, kMaxCustomParams> m_customParams;
486 std::array<QColor, kMaxCustomColors> m_customColors;
487
488 // ── Extra Bindings (consumer-managed) ──────────────────────────────
489 struct ExtraBinding
490 {
491 QRhiTexture* texture = nullptr;
492 QRhiSampler* sampler = nullptr;
493 };
494 // std::map (ordered) so SRB construction produces a deterministic binding
495 // order regardless of insertion/erasure history. An unordered_map can
496 // produce different iteration orders after erasures, which Qt RHI
497 // backends may hash into different pipeline layout signatures.
498 std::map<int, ExtraBinding> m_extraBindings;
499 bool m_extraBindingsDirty = false;
500
501 // ── 1x1 Transparent Fallback ───────────────────────────────────────
502 QImage m_transparentFallbackImage;
503
504 // ── Audio spectrum texture (binding 6) ─────────────────────────────
505 QVector<float> m_audioSpectrum;
506 std::unique_ptr<QRhiTexture> m_audioSpectrumTexture;
507 std::unique_ptr<QRhiSampler> m_audioSpectrumSampler;
508 bool m_audioSpectrumDirty = false;
509
510 // ── User texture slots (bindings 7-10) ─────────────────────────────
511 std::array<QImage, kMaxUserTextures> m_userTextureImages;
512 std::array<std::unique_ptr<QRhiTexture>, kMaxUserTextures> m_userTextures;
513 std::array<std::unique_ptr<QRhiSampler>, kMaxUserTextures> m_userTextureSamplers;
514 std::array<QString, kMaxUserTextures> m_userTextureWraps;
515 std::array<bool, kMaxUserTextures> m_userTextureDirty = {};
516
517 // ── Source texture override (slot 0 / binding 7) ───────────────────
518 // Texture-provider source — typically a `QQuickItem::textureProvider()`
519 // for a layer-enabled item. When non-null this supersedes
520 // m_userTextures[0] in the SRB build, so the shader's uTexture0
521 // samples a live QML render instead of a static QImage upload.
522 // m_sourceSampler is owned here (not a user-texture sampler) so we
523 // can keep slot 0's user-texture sampler available for callers that
524 // mix the two paths. m_lastSourceRhiTexture caches the QRhiTexture*
525 // we last bound; when the provider's underlying texture changes
526 // (FBO recreation on resize / device-loss) we drop the SRB so the
527 // next rebuild picks up the new pointer.
528 QPointer<QSGTextureProvider> m_sourceTextureProvider;
529 std::unique_ptr<QRhiSampler> m_sourceSampler;
530 QRhiTexture* m_lastSourceRhiTexture = nullptr;
535 bool m_sourceSamplerFailed = false;
539 bool m_warnedForeignRhi = false;
547 std::unique_ptr<QRhiTexture> m_transparentFallbackTexture;
548 bool m_transparentFallbackTextureNeedsUpload = false;
549
550 // ── Depth buffer (binding 12) ──────────────────────────────────────
551 bool m_useDepthBuffer = false;
552 bool m_depthMultiBufferWarned = false;
553 std::unique_ptr<QRhiTexture> m_depthTexture;
554 std::unique_ptr<QRhiSampler> m_depthSampler;
555
556 // ── Desktop wallpaper texture (binding 11) ─────────────────────────
557 bool m_useWallpaper = false;
558 QImage m_wallpaperImage;
559 std::unique_ptr<QRhiTexture> m_wallpaperTexture;
560 std::unique_ptr<QRhiSampler> m_wallpaperSampler;
561 bool m_wallpaperDirty = false;
562};
563
566{
567 bool success = false;
569};
570
594 const QString& vertexPath, const QString& fragmentPath, const QStringList& includePaths = {},
595 const QString& paramPreamble = {}, const QString& entryPrologue = {},
596 const QList<PhosphorShaders::EntryCandidate>& entryCandidates = {});
597
598} // namespace PhosphorRendering
QSGRenderNode for fullscreen-quad shader rendering via Qt RHI (Vulkan / OpenGL)
Definition ShaderNodeRhi.h:115
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:140
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:218
void render(const RenderState *state) override
static QString normalizeFilterMode(const QString &filter)
Normalize filter mode string to "nearest", "linear", or "mipmap".
void setEntryScaffold(const QString &prologue, const QList< PhosphorShaders::EntryCandidate > &candidates)
Install the entry-point scaffold for the fragment stage.
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)
void setParamPreamble(const QString &preamble)
Set the generated named-parameter preamble that is spliced after the fragment shader's #version line ...
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:40
constexpr int kReservedBindingRangeEnd
Last library-managed binding.
Definition ShaderNodeRhi.h:62
constexpr int kMaxCustomColors
Definition ShaderNodeRhi.h:43
PHOSPHORRENDERING_EXPORT WarmShaderBakeResult warmShaderBakeCacheForPaths(const QString &vertexPath, const QString &fragmentPath, const QStringList &includePaths={}, const QString &paramPreamble={}, const QString &entryPrologue={}, const QList< PhosphorShaders::EntryCandidate > &entryCandidates={})
Pre-load cache warming: load, bake, and insert shaders for the given paths into the shared bake cache...
constexpr int kFirstFreeConsumerBinding
First slot usable via setExtraBinding()
Definition ShaderNodeRhi.h:55
constexpr int kReservedBindingRangeStart
First library-managed binding above 0.
Definition ShaderNodeRhi.h:61
constexpr bool isConsumerBinding(int binding) noexcept
Definition ShaderNodeRhi.h:70
constexpr int kMaxConsumerBinding
Highest portable SRB binding.
Definition ShaderNodeRhi.h:60
constexpr int kMaxUserTextures
Definition ShaderNodeRhi.h:41
constexpr int kUserTextureBaseBinding
First SRB binding for the user-texture slots (slot 0 → binding 7).
Definition ShaderNodeRhi.h:67
constexpr int kMaxCustomParams
Definition ShaderNodeRhi.h:42
Result of warmShaderBakeCacheForPaths for reporting to UI.
Definition ShaderNodeRhi.h:566
QString errorMessage
Definition ShaderNodeRhi.h:568
bool success
Definition ShaderNodeRhi.h:567
GPU uniform buffer layout following std140 rules (base region).
Definition BaseUniforms.h:37