Loading...
Searching...
No Matches
RenderState.hpp
1#pragma once
2#include <QOffscreenSurface>
3#include <QtGui/private/qrhi_p.h>
4
5#include <score_plugin_gfx_export.h>
6
7#include <functional>
8
9#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
10using QRhiBufferReadbackResult = QRhiReadbackResult;
11#endif
12
13class QOffscreenSurface;
14class QWindow;
15namespace score::gfx
16{
17class RenderList;
22{
23 Null,
24 OpenGL,
25 Vulkan,
26 D3D11,
27 Metal,
28 D3D12
29};
30
31class Window;
32
37{
38 RenderState() = default;
39 RenderState(const RenderState&) = delete;
40 RenderState(RenderState&&) = delete;
41 RenderState& operator=(const RenderState&) = delete;
42 RenderState& operator=(RenderState&&) = delete;
43
44 QRhi* rhi{};
45 QRhiRenderPassDescriptor* renderPassDescriptor{};
46 std::weak_ptr<RenderList> renderer{};
47
48 QOffscreenSurface* surface{};
49 std::weak_ptr<score::gfx::Window>
50 window{}; // Not always set, only used to get mouse events & such.
51 QSize renderSize{};
52 QSize outputSize{};
53 int samples{1};
54 QRhiTexture::Format renderFormat{QRhiTexture::RGBA8};
55 GraphicsApi api{};
56 QShaderVersion version{};
57
58 struct Caps
59 {
60 // Indirect draw — Qt 6.12+; populated only on compatible builds.
61 bool drawIndirect{false};
62 bool drawIndirectMulti{false};
63
64 // GPU-decided draw count / dispatch size — Qt 6.13-era QRhi API
65 // (QRhi::DrawIndirectCount, QRhi::DispatchIndirect). Populated through
66 // RhiIndirectCompat.hpp's member DETECTION, not QT_VERSION: the ossia SDK
67 // pins the Qt 6.12 branch with the 6.13 indirect changes cherry-picked,
68 // so version macros lie on the reference builds (see that header).
69 //
70 // Fallback ladder these caps select, from most to least capable — every
71 // rung must paint the SAME pixels (GfxIndirectFallbackLadder.cpp holds
72 // each rung to that):
73 // drawIndirectCount → drawIndexedIndirectCount / drawIndirectCount
74 // (count read by the GPU from a count buffer)
75 // drawIndirect+multi → one drawIndexedIndirect of the full command
76 // capacity (producers MUST leave command slots
77 // beyond their GPU-written count zeroed: a zero
78 // command draws nothing, so capacity == count)
79 // drawIndirect only → per-command loop of drawCount=1 indirect draws
80 // none → CPU loop over cpuDrawCommands, filled by the
81 // producer or by the readback fallback (which also
82 // reads the count buffer and clamps).
83 //
84 // Kill switches (Caps::populate): SCORE_GFX_NO_GPU_INDIRECT_COUNT,
85 // SCORE_GFX_NO_GPU_INDIRECT_MULTI, SCORE_GFX_NO_GPU_DISPATCH_INDIRECT,
86 // and SCORE_GFX_NO_GPU_INDIRECT, which forces the CPU rung outright.
87 // Each fallback stays exercisable on capable hardware.
88 bool drawIndirectCount{false};
89 bool dispatchIndirect{false};
90
91 // Always queryable.
92 bool multiview{false};
93 bool resolveDepthStencil{false};
94 bool tessellation{false};
95 bool geometryShader{false};
96
97 // Extended capability set, driving shader feature gating and observability.
98 //
99 // baseInstance: indirect draws can use firstInstance as the draw ID through
100 // gl_BaseInstance (ARB_shader_draw_parameters), which MDI's per-draw lookup
101 // reads.
102 // instanceIndexIncludesBaseInstance: whether gl_InstanceIndex already contains
103 // the firstInstance offset. The shader prepass injects
104 // SCORE_INSTANCE_INDEX_INCLUDES_BASE_INSTANCE from this so presets work on
105 // both paths.
106 // variableRateShading: per-tile shading-rate maps
107 // (VK_EXT_fragment_shading_rate, D3D12 VRS).
108 // timestamps: whether lastCompletedGpuTime() returns meaningful values.
109 // pipelineCacheDataLoadSave: pipeline binary cache round-trip, used by
110 // tryLoadPipelineCache / tryStorePipelineCache.
111 // textureViewFormat: R32UI <-> R32F aliasing, needed by the visibility buffer
112 // preset.
113 // depthClamp: reverse-Z shadow passes avoiding near-plane clipping.
114 bool baseInstance{false};
115 bool instanceIndexIncludesBaseInstance{false};
116 bool variableRateShading{false};
117 bool timestamps{false};
118 bool pipelineCacheDataLoadSave{false};
119 bool textureViewFormat{false};
120 bool depthClamp{false};
121
122 void populate(QRhi& rhi);
123 } caps;
124
125 // Called after QRhi is destroyed to clean up an imported VkDevice
126 std::function<void()> customDeviceCleanup;
127
128 // Called right before the QRhi is destroyed, while its pipeline cache is
129 // still accessible. Used to persist QRhi::pipelineCacheData() to disk.
130 std::function<void()> preRhiDestroy;
131
132 // Mid-session pipeline-cache flush. Same storage path
133 // as preRhiDestroy but callable during normal operation — invoked
134 // from RenderList::render after a PSO-compile burst so the cache
135 // survives crashes / force-quits without a clean shutdown. Null
136 // when the backend doesn't support PipelineCacheDataLoadSave.
137 std::function<void()> savePipelineCache;
138
139 void destroy()
140 {
141 window.reset();
142
143 if(preRhiDestroy)
144 {
145 preRhiDestroy();
146 preRhiDestroy = nullptr;
147 }
148
149 delete rhi;
150 rhi = nullptr;
151
152 // Destroy imported VkDevice AFTER QRhi (which still references it during shutdown)
153 if(customDeviceCleanup)
154 {
155 customDeviceCleanup();
156 customDeviceCleanup = nullptr;
157 }
158
159 delete surface;
160 surface = nullptr;
161 }
162};
163
175{
176 Owned,
177 Cached
178};
179
180SCORE_PLUGIN_GFX_EXPORT
181std::shared_ptr<RenderState> createRenderState(
182 GraphicsApi graphicsApi, QSize sz, QWindow* window,
183 SharedDeviceMode deviceMode = SharedDeviceMode::Owned);
184
185static const constexpr int32_t invalid_node_index = -1;
186
222inline QRhiBuffer::UsageFlags
223compatibleBufferUsage(QRhi& rhi, QRhiBuffer::UsageFlags usage) noexcept
224{
225 if(!usage.testFlag(QRhiBuffer::StorageBuffer))
226 return usage;
227 // Storage-only: there is no other role to fall back to, keep it as asked.
228 if(int(usage) == int(QRhiBuffer::StorageBuffer))
229 return usage;
230 // QRhi::Compute is exactly the GL backend's own SSBO line: caps.compute is
231 // set for GL >= 4.3 / GLES >= 3.1 (qrhigles2.cpp), the same versions that
232 // introduce GL_SHADER_STORAGE_BUFFER. Ask the capability, do not name a
233 // backend: it stays right if a backend gains or loses the ability.
234 if(rhi.isFeatureSupported(QRhi::Compute))
235 return usage;
236 return usage & ~QRhiBuffer::UsageFlags(QRhiBuffer::StorageBuffer);
237}
238
259inline bool
260indirectDrawBreaksMultiView(GraphicsApi api, int multiViewCount) noexcept
261{
262 return multiViewCount >= 2 && api == GraphicsApi::Metal;
263}
264
286inline bool
287drawIndirectCountUsable(GraphicsApi api, int multiViewCount) noexcept
288{
289 if(indirectDrawBreaksMultiView(api, multiViewCount))
290 return false;
291 if(api == GraphicsApi::Metal)
292 return false;
293 return true;
294}
295
317 GraphicsApi api, const QShaderVersion& version, int multiViewCount) noexcept
318{
319 // SCORE_GFX_DISABLE_MULTIVIEW means "pretend this backend has no multiview".
320 // It also makes this path -- otherwise reachable only on a D3D target --
321 // testable everywhere.
322 if(qEnvironmentVariableIsSet("SCORE_GFX_DISABLE_MULTIVIEW"))
323 return true;
324
325 if(api != GraphicsApi::D3D11 && api != GraphicsApi::D3D12)
326 return false;
327
328 // D3D11 is pinned to SM 5.0 for good, so it can never have SV_ViewID.
329 if(api == GraphicsApi::D3D11)
330 return true;
331
332 // D3D12 without dxcompiler.dll drops to SM 5.0, which has no SV_ViewID
333 // either. The ossia SDK ships that runtime, so shipping builds DO reach
334 // 6.1 -- but a source build without it still lands here.
335 if(version.version() < 61)
336 return true;
337
338 // D3D12 ViewInstancing is capped at D3D12_MAX_VIEW_INSTANCE_COUNT == 4.
339 // Above that, CreatePipelineState rejects the PSO outright:
340 //
341 // Failed to create graphics pipeline state: COM error 0x80070057:
342 // The parameter is incorrect.
343 // Warning! MRT Pipeline not created
344 //
345 // and with no pipeline nothing draws, so every cube face reads back
346 // (0,0,0,255) -- which looks like a rasterizer or copy fault and is neither.
347 // Qt encodes the same limit in its QVarLengthArray<D3D12_VIEW_INSTANCE_LOCATION, 4>.
348 //
349 // A cubemap is inherently 6 views, so CUBEMAP+MULTIVIEW can never use D3D12
350 // ViewInstancing -- it is not a Qt bug and not a shim bug, it is the API's
351 // limit.
352 //
353 // Keep the fast path where it is legal: 2- and 4-view shaders still get real
354 // ViewInstancing. Only what D3D12 cannot express falls back to N passes.
355 if(multiViewCount > 4)
356 return true;
357
358 return false;
359}
360
361}
Graphics rendering pipeline for ossia score.
Definition Filter/PreviewWidget.hpp:11
bool indirectDrawBreaksMultiView(GraphicsApi api, int multiViewCount) noexcept
Whether a GPU indirect draw silently loses multiview on this backend.
Definition RenderState.hpp:260
GraphicsApi
Available graphics APIs to use.
Definition RenderState.hpp:22
bool drawIndirectCountUsable(GraphicsApi api, int multiViewCount) noexcept
Whether the GPU-count draw path (drawIndexedIndirectCount) is usable.
Definition RenderState.hpp:287
SharedDeviceMode
Who owns the imported Vulkan device behind a RenderState.
Definition RenderState.hpp:175
bool viewIndexNeedsPassIndexFallback(GraphicsApi api, const QShaderVersion &version, int multiViewCount) noexcept
Whether MULTIVIEW must be emulated with one pass per view.
Definition RenderState.hpp:316
QRhiBuffer::UsageFlags compatibleBufferUsage(QRhi &rhi, QRhiBuffer::UsageFlags usage) noexcept
Drop a StorageBuffer usage the backend cannot actually honour.
Definition RenderState.hpp:223
Definition RenderState.hpp:59
Global state associated to a rendering context.
Definition RenderState.hpp:37