/src/qtbase/src/gui/rhi/qrhi_p.h
Line | Count | Source |
1 | | // Copyright (C) 2023 The Qt Company Ltd. |
2 | | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | | // Qt-Security score:significant reason:default |
4 | | |
5 | | #ifndef QRHI_P_H |
6 | | #define QRHI_P_H |
7 | | |
8 | | // |
9 | | // W A R N I N G |
10 | | // ------------- |
11 | | // |
12 | | // This file is not part of the Qt API. It exists purely as an |
13 | | // implementation detail. This header file may change from version to |
14 | | // version without notice, or even be removed. |
15 | | // |
16 | | // We mean it. |
17 | | // |
18 | | |
19 | | #include <rhi/qrhi.h> |
20 | | #include <QBitArray> |
21 | | #include <QAtomicInt> |
22 | | #include <QElapsedTimer> |
23 | | #include <QLoggingCategory> |
24 | | #include <QtCore/qset.h> |
25 | | #include <QtCore/qvarlengtharray.h> |
26 | | #include <QtCore/private/qflatmap_p.h> |
27 | | |
28 | | QT_BEGIN_NAMESPACE |
29 | | |
30 | 0 | #define QRHI_RES(t, x) static_cast<t *>(x) |
31 | 0 | #define QRHI_RES_RHI(t) t *rhiD = static_cast<t *>(m_rhi) |
32 | | |
33 | | Q_DECLARE_LOGGING_CATEGORY(QRHI_LOG_INFO) |
34 | | Q_DECLARE_LOGGING_CATEGORY(QRHI_LOG_RUB) |
35 | | |
36 | | class QRhiImplementation |
37 | | { |
38 | | public: |
39 | | virtual ~QRhiImplementation(); |
40 | | |
41 | | virtual bool create(QRhi::Flags flags) = 0; |
42 | | virtual void destroy() = 0; |
43 | | virtual QRhi::AdapterList enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const; |
44 | | |
45 | | virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0; |
46 | | virtual QRhiComputePipeline *createComputePipeline() = 0; |
47 | | virtual QRhiShaderResourceBindings *createShaderResourceBindings() = 0; |
48 | | virtual QRhiBuffer *createBuffer(QRhiBuffer::Type type, |
49 | | QRhiBuffer::UsageFlags usage, |
50 | | quint32 size) = 0; |
51 | | virtual QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, |
52 | | const QSize &pixelSize, |
53 | | int sampleCount, |
54 | | QRhiRenderBuffer::Flags flags, |
55 | | QRhiTexture::Format backingFormatHint) = 0; |
56 | | virtual QRhiTexture *createTexture(QRhiTexture::Format format, |
57 | | const QSize &pixelSize, |
58 | | int depth, |
59 | | int arraySize, |
60 | | int sampleCount, |
61 | | QRhiTexture::Flags flags) = 0; |
62 | | virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, |
63 | | QRhiSampler::Filter minFilter, |
64 | | QRhiSampler::Filter mipmapMode, |
65 | | QRhiSampler:: AddressMode u, |
66 | | QRhiSampler::AddressMode v, |
67 | | QRhiSampler::AddressMode w) = 0; |
68 | | |
69 | | virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, |
70 | | QRhiTextureRenderTarget::Flags flags) = 0; |
71 | | |
72 | | virtual QRhiShadingRateMap *createShadingRateMap() = 0; |
73 | | |
74 | | virtual QRhiSwapChain *createSwapChain() = 0; |
75 | | virtual QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) = 0; |
76 | | virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) = 0; |
77 | | virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) = 0; |
78 | | virtual QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) = 0; |
79 | | virtual QRhi::FrameOpResult finish() = 0; |
80 | | |
81 | | virtual void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; |
82 | | |
83 | | virtual void beginPass(QRhiCommandBuffer *cb, |
84 | | QRhiRenderTarget *rt, |
85 | | const QColor &colorClearValue, |
86 | | const QRhiDepthStencilClearValue &depthStencilClearValue, |
87 | | QRhiResourceUpdateBatch *resourceUpdates, |
88 | | QRhiCommandBuffer::BeginPassFlags flags) = 0; |
89 | | virtual void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; |
90 | | |
91 | | virtual void setGraphicsPipeline(QRhiCommandBuffer *cb, |
92 | | QRhiGraphicsPipeline *ps) = 0; |
93 | | |
94 | | virtual void setShaderResources(QRhiCommandBuffer *cb, |
95 | | QRhiShaderResourceBindings *srb, |
96 | | int dynamicOffsetCount, |
97 | | const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) = 0; |
98 | | |
99 | | virtual void setVertexInput(QRhiCommandBuffer *cb, |
100 | | int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, |
101 | | QRhiBuffer *indexBuf, quint32 indexOffset, |
102 | | QRhiCommandBuffer::IndexFormat indexFormat) = 0; |
103 | | |
104 | | virtual void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) = 0; |
105 | | virtual void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) = 0; |
106 | | virtual void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) = 0; |
107 | | virtual void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) = 0; |
108 | | virtual void setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize) = 0; |
109 | | |
110 | | virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount, |
111 | | quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0; |
112 | | virtual void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, |
113 | | quint32 instanceCount, quint32 firstIndex, |
114 | | qint32 vertexOffset, quint32 firstInstance) = 0; |
115 | | |
116 | | virtual void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) = 0; |
117 | | virtual void debugMarkEnd(QRhiCommandBuffer *cb) = 0; |
118 | | virtual void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) = 0; |
119 | | |
120 | | virtual void beginComputePass(QRhiCommandBuffer *cb, |
121 | | QRhiResourceUpdateBatch *resourceUpdates, |
122 | | QRhiCommandBuffer::BeginPassFlags flags) = 0; |
123 | | virtual void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0; |
124 | | virtual void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) = 0; |
125 | | virtual void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) = 0; |
126 | | |
127 | | virtual const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) = 0; |
128 | | virtual void beginExternal(QRhiCommandBuffer *cb) = 0; |
129 | | virtual void endExternal(QRhiCommandBuffer *cb) = 0; |
130 | | virtual double lastCompletedGpuTime(QRhiCommandBuffer *cb) = 0; |
131 | | |
132 | | virtual QList<int> supportedSampleCounts() const = 0; |
133 | | virtual int ubufAlignment() const = 0; |
134 | | virtual QList<QSize> supportedShadingRates(int sampleCount) const = 0; |
135 | | virtual bool isYUpInFramebuffer() const = 0; |
136 | | virtual bool isYUpInNDC() const = 0; |
137 | | virtual bool isClipDepthZeroToOne() const = 0; |
138 | | virtual QMatrix4x4 clipSpaceCorrMatrix() const = 0; |
139 | | virtual bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const = 0; |
140 | | virtual bool isFeatureSupported(QRhi::Feature feature) const = 0; |
141 | | virtual int resourceLimit(QRhi::ResourceLimit limit) const = 0; |
142 | | virtual const QRhiNativeHandles *nativeHandles() = 0; |
143 | | virtual QRhiDriverInfo driverInfo() const = 0; |
144 | | virtual QRhiStats statistics() = 0; |
145 | | virtual bool makeThreadLocalNativeContextCurrent() = 0; |
146 | | virtual void setQueueSubmitParams(QRhiNativeHandles *params) = 0; |
147 | | virtual void releaseCachedResources() = 0; |
148 | | virtual bool isDeviceLost() const = 0; |
149 | | |
150 | | virtual QByteArray pipelineCacheData() = 0; |
151 | | virtual void setPipelineCacheData(const QByteArray &data) = 0; |
152 | | |
153 | | static QRhiImplementation *newInstance(QRhi::Implementation impl, QRhiInitParams *params, QRhiNativeHandles *importDevice); |
154 | | void prepareForCreate(QRhi *rhi, QRhi::Implementation impl, QRhi::Flags flags, QRhiAdapter *adapter); |
155 | | |
156 | | bool isCompressedFormat(QRhiTexture::Format format) const; |
157 | | void compressedFormatInfo(QRhiTexture::Format format, const QSize &size, |
158 | | quint32 *bpl, quint32 *byteSize, |
159 | | QSize *blockDim) const; |
160 | | void textureFormatInfo(QRhiTexture::Format format, const QSize &size, |
161 | | quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const; |
162 | | bool isStencilSupportingFormat(QRhiTexture::Format format) const; |
163 | | |
164 | | void registerResource(QRhiResource *res, bool ownsNativeResources = true) |
165 | 0 | { |
166 | | // The ownsNativeResources is relevant for the (graphics resource) leak |
167 | | // check in ~QRhiImplementation; when false, the registration's sole |
168 | | // purpose is to automatically null out the resource's m_rhi pointer in |
169 | | // case the rhi goes away first. (which should not happen in |
170 | | // well-written applications but we try to be graceful) |
171 | 0 | resources.insert(res, ownsNativeResources); |
172 | 0 | } |
173 | | |
174 | | void unregisterResource(QRhiResource *res) |
175 | 0 | { |
176 | 0 | resources.remove(res); |
177 | 0 | } |
178 | | |
179 | | void addDeleteLater(QRhiResource *res) |
180 | 0 | { |
181 | 0 | if (inFrame) |
182 | 0 | pendingDeleteResources.insert(res); |
183 | 0 | else |
184 | 0 | delete res; |
185 | 0 | } |
186 | | |
187 | | void addCleanupCallback(const QRhi::CleanupCallback &callback) |
188 | 0 | { |
189 | 0 | cleanupCallbacks.append(callback); |
190 | 0 | } |
191 | | |
192 | | void addCleanupCallback(const void *key, const QRhi::CleanupCallback &callback) |
193 | 0 | { |
194 | 0 | keyedCleanupCallbacks[key] = callback; |
195 | 0 | } |
196 | | |
197 | | void removeCleanupCallback(const void *key) |
198 | 0 | { |
199 | 0 | keyedCleanupCallbacks.remove(key); |
200 | 0 | } |
201 | | |
202 | | bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps); |
203 | | bool sanityCheckShaderResourceBindings(QRhiShaderResourceBindings *srb); |
204 | | void updateLayoutDesc(QRhiShaderResourceBindings *srb); |
205 | | |
206 | | quint32 pipelineCacheRhiId() const |
207 | 0 | { |
208 | 0 | const quint32 ver = (QT_VERSION_MAJOR << 16) | (QT_VERSION_MINOR << 8) | (QT_VERSION_PATCH); |
209 | 0 | return (quint32(implType) << 24) | ver; |
210 | 0 | } |
211 | | |
212 | | void pipelineCreationStart() |
213 | 0 | { |
214 | 0 | pipelineCreationTimer.start(); |
215 | 0 | } |
216 | | |
217 | | void pipelineCreationEnd() |
218 | 0 | { |
219 | 0 | accumulatedPipelineCreationTime += pipelineCreationTimer.elapsed(); |
220 | 0 | } |
221 | | |
222 | | qint64 totalPipelineCreationTime() const |
223 | 0 | { |
224 | 0 | return accumulatedPipelineCreationTime; |
225 | 0 | } |
226 | | |
227 | | QRhiVertexInputAttribute::Format shaderDescVariableFormatToVertexInputFormat(QShaderDescription::VariableType type) const; |
228 | | quint32 byteSizePerVertexForVertexInputFormat(QRhiVertexInputAttribute::Format format) const; |
229 | | |
230 | | static const QRhiShaderResourceBinding::Data *shaderResourceBindingData(const QRhiShaderResourceBinding &binding) |
231 | 0 | { |
232 | 0 | return &binding.d; |
233 | 0 | } |
234 | | |
235 | | static QRhiShaderResourceBinding::Data *shaderResourceBindingData(QRhiShaderResourceBinding &binding) |
236 | 0 | { |
237 | 0 | return &binding.d; |
238 | 0 | } |
239 | | |
240 | | static bool sortedBindingLessThan(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) |
241 | 0 | { |
242 | 0 | return a.d.binding < b.d.binding; |
243 | 0 | } |
244 | | |
245 | | int effectiveSampleCount(int sampleCount) const; |
246 | | QSize clampedSubResourceUploadSize(QSize size, QPoint dstPos, int level, QSize textureSizeAtLevelZero, bool warn = true); |
247 | | |
248 | | void runCleanup(); |
249 | | |
250 | | QRhi *q; |
251 | | |
252 | | static const int MAX_SHADER_CACHE_ENTRIES = 128; |
253 | | |
254 | | bool debugMarkers = false; |
255 | | int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11. |
256 | | bool inFrame = false; |
257 | | |
258 | | QRhiAdapter *requestedRhiAdapter = nullptr; |
259 | | |
260 | | private: |
261 | | QRhi::Implementation implType; |
262 | | QThread *implThread; |
263 | | QVarLengthArray<QRhiResourceUpdateBatch *, 4> resUpdPool; |
264 | | quint64 resUpdPoolMap = 0; |
265 | | int lastResUpdIdx = -1; |
266 | | QHash<QRhiResource *, bool> resources; |
267 | | QSet<QRhiResource *> pendingDeleteResources; |
268 | | QVarLengthArray<QRhi::CleanupCallback, 4> cleanupCallbacks; |
269 | | QHash<const void *, QRhi::CleanupCallback> keyedCleanupCallbacks; |
270 | | QElapsedTimer pipelineCreationTimer; |
271 | | qint64 accumulatedPipelineCreationTime = 0; |
272 | | |
273 | | friend class QRhi; |
274 | | friend class QRhiResourceUpdateBatchPrivate; |
275 | | friend class QRhiBufferData; |
276 | | }; |
277 | | |
278 | | enum QRhiTargetRectBoundMode |
279 | | { |
280 | | UnBounded, |
281 | | Bounded |
282 | | }; |
283 | | |
284 | | template<QRhiTargetRectBoundMode boundingMode, typename T, size_t N> |
285 | | bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array<T, N> &r, |
286 | | T *x, T *y, T *w, T *h) |
287 | | { |
288 | | // x,y are bottom-left in QRhiScissor and QRhiViewport but top-left in |
289 | | // Vulkan/Metal/D3D. Our input is an OpenGL-style scissor rect where both |
290 | | // negative x or y, and partly or completely out of bounds rects are |
291 | | // allowed. The only thing the input here cannot have is a negative width |
292 | | // or height. We must handle all other input gracefully, clamping to a zero |
293 | | // width or height rect in the worst case, and ensuring the resulting rect |
294 | | // is inside the rendertarget's bounds because some APIs' validation/debug |
295 | | // layers are allergic to out of bounds scissor rects. |
296 | | |
297 | | const T outputWidth = outputSize.width(); |
298 | | const T outputHeight = outputSize.height(); |
299 | | const T inputWidth = r[2]; |
300 | | const T inputHeight = r[3]; |
301 | | |
302 | | if (inputWidth < 0 || inputHeight < 0) |
303 | | return false; |
304 | | |
305 | | *x = r[0]; |
306 | | *y = outputHeight - (r[1] + inputHeight); |
307 | | *w = inputWidth; |
308 | | *h = inputHeight; |
309 | | |
310 | | if (boundingMode == Bounded) { |
311 | | const T widthOffset = *x < 0 ? -*x : 0; |
312 | | const T heightOffset = *y < 0 ? -*y : 0; |
313 | | *w = *x < outputWidth ? qMax<T>(0, inputWidth - widthOffset) : 0; |
314 | | *h = *y < outputHeight ? qMax<T>(0, inputHeight - heightOffset) : 0; |
315 | | |
316 | | if (outputWidth > 0) |
317 | | *x = qBound<T>(0, *x, outputWidth - 1); |
318 | | if (outputHeight > 0) |
319 | | *y = qBound<T>(0, *y, outputHeight - 1); |
320 | | |
321 | | if (*x + *w > outputWidth) |
322 | | *w = qMax<T>(0, outputWidth - *x); |
323 | | if (*y + *h > outputHeight) |
324 | | *h = qMax<T>(0, outputHeight - *y); |
325 | | } |
326 | | return true; |
327 | | } |
328 | | |
329 | | struct QRhiBufferDataPrivate |
330 | | { |
331 | | Q_DISABLE_COPY_MOVE(QRhiBufferDataPrivate) |
332 | 0 | QRhiBufferDataPrivate() { } // don't value-initialize smallData |
333 | | int ref = 1; |
334 | | quint32 size = 0; |
335 | | QByteArray largeData; |
336 | | static constexpr quint32 SMALL_DATA_SIZE = 1024; |
337 | | char smallData[SMALL_DATA_SIZE]; |
338 | | }; |
339 | | |
340 | | // no detach-with-contents, no atomic refcount, no shrink |
341 | | class QRhiBufferData |
342 | | { |
343 | | public: |
344 | 0 | QRhiBufferData() = default; |
345 | | ~QRhiBufferData() |
346 | 0 | { |
347 | 0 | if (d && !--d->ref) |
348 | 0 | delete d; |
349 | 0 | } |
350 | | QRhiBufferData(const QRhiBufferData &other) |
351 | 0 | : d(other.d) |
352 | 0 | { |
353 | 0 | if (d) |
354 | 0 | d->ref += 1; |
355 | 0 | } |
356 | | QRhiBufferData &operator=(const QRhiBufferData &other) |
357 | 0 | { |
358 | 0 | if (d == other.d) |
359 | 0 | return *this; |
360 | 0 | if (other.d) |
361 | 0 | other.d->ref += 1; |
362 | 0 | if (d && !--d->ref) |
363 | 0 | delete d; |
364 | 0 | d = other.d; |
365 | 0 | return *this; |
366 | 0 | } |
367 | | const char *constData() const |
368 | 0 | { |
369 | 0 | return d ? (d->size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE ? d->smallData : d->largeData.constData()) : nullptr; |
370 | 0 | } |
371 | | quint32 size() const |
372 | 0 | { |
373 | 0 | return d ? d->size : 0; |
374 | 0 | } |
375 | | quint32 largeAlloc() const |
376 | 0 | { |
377 | 0 | return d ? d->largeData.size() : 0; |
378 | 0 | } |
379 | | void assign(const char *s, quint32 size) |
380 | 0 | { |
381 | 0 | if (!d) { |
382 | 0 | d = new QRhiBufferDataPrivate; |
383 | 0 | } else if (d->ref != 1) { |
384 | 0 | if (QRHI_LOG_RUB().isDebugEnabled()) |
385 | 0 | qDebug("[rub] QRhiBufferData %p/%p new backing due to no-copy detach, ref was %d", this, d, d->ref); |
386 | 0 | d->ref -= 1; |
387 | 0 | d = new QRhiBufferDataPrivate; |
388 | 0 | } |
389 | 0 | d->size = size; |
390 | 0 | if (size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE) { |
391 | 0 | memcpy(d->smallData, s, size); |
392 | 0 | } else { |
393 | 0 | if (QRHI_LOG_RUB().isDebugEnabled() && largeAlloc() < size) |
394 | 0 | qDebug("[rub] QRhiBufferData %p/%p new large data allocation %u -> %u", this, d, largeAlloc(), size); |
395 | 0 | d->largeData.assign(QByteArrayView(s, size)); // keeps capacity |
396 | 0 | } |
397 | 0 | } |
398 | | void assign(QByteArray data) |
399 | 0 | { |
400 | 0 | if (!d) { |
401 | 0 | d = new QRhiBufferDataPrivate; |
402 | 0 | } else if (d->ref != 1) { |
403 | 0 | if (QRHI_LOG_RUB().isDebugEnabled()) |
404 | 0 | qDebug("[rub] QRhiBufferData %p/%p new backing due to no-copy detach, ref was %d", this, d, d->ref); |
405 | 0 | d->ref -= 1; |
406 | 0 | d = new QRhiBufferDataPrivate; |
407 | 0 | } |
408 | 0 | d->size = data.size(); |
409 | 0 | if (d->size <= QRhiBufferDataPrivate::SMALL_DATA_SIZE) { |
410 | 0 | memcpy(d->smallData, data.constData(), data.size()); |
411 | 0 | } else { |
412 | 0 | d->largeData = std::move(data); |
413 | 0 | } |
414 | 0 | } |
415 | | private: |
416 | | QRhiBufferDataPrivate *d = nullptr; |
417 | | }; |
418 | | |
419 | | Q_DECLARE_TYPEINFO(QRhiBufferData, Q_RELOCATABLE_TYPE); |
420 | | |
421 | | class QRhiResourceUpdateBatchPrivate |
422 | | { |
423 | | public: |
424 | | struct BufferOp { |
425 | | enum Type { |
426 | | DynamicUpdate, |
427 | | StaticUpload, |
428 | | Read |
429 | | }; |
430 | | Type type; |
431 | | QRhiBuffer *buf; |
432 | | quint32 offset; |
433 | | QRhiBufferData data; |
434 | | quint32 readSize; |
435 | | QRhiReadbackResult *result; |
436 | | |
437 | | static BufferOp dynamicUpdate(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) |
438 | 0 | { |
439 | 0 | BufferOp op = {}; |
440 | 0 | changeToDynamicUpdate(&op, buf, offset, size, data); |
441 | 0 | return op; |
442 | 0 | } |
443 | | |
444 | | static void changeToDynamicUpdate(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) |
445 | 0 | { |
446 | 0 | op->type = DynamicUpdate; |
447 | 0 | op->buf = buf; |
448 | 0 | op->offset = offset; |
449 | 0 | const int effectiveSize = size ? size : buf->size(); |
450 | 0 | op->data.assign(reinterpret_cast<const char *>(data), effectiveSize); |
451 | 0 | } |
452 | | |
453 | | static BufferOp dynamicUpdate(QRhiBuffer *buf, quint32 offset, QByteArray data) |
454 | 0 | { |
455 | 0 | BufferOp op = {}; |
456 | 0 | changeToDynamicUpdate(&op, buf, offset, std::move(data)); |
457 | 0 | return op; |
458 | 0 | } |
459 | | |
460 | | static void changeToDynamicUpdate(BufferOp *op, QRhiBuffer *buf, quint32 offset, QByteArray data) |
461 | 0 | { |
462 | 0 | op->type = DynamicUpdate; |
463 | 0 | op->buf = buf; |
464 | 0 | op->offset = offset; |
465 | 0 | op->data.assign(std::move(data)); |
466 | 0 | } |
467 | | |
468 | | static BufferOp staticUpload(QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) |
469 | 0 | { |
470 | 0 | BufferOp op = {}; |
471 | 0 | changeToStaticUpload(&op, buf, offset, size, data); |
472 | 0 | return op; |
473 | 0 | } |
474 | | |
475 | | static void changeToStaticUpload(BufferOp *op, QRhiBuffer *buf, quint32 offset, quint32 size, const void *data) |
476 | 0 | { |
477 | 0 | op->type = StaticUpload; |
478 | 0 | op->buf = buf; |
479 | 0 | op->offset = offset; |
480 | 0 | const int effectiveSize = size ? size : buf->size(); |
481 | 0 | op->data.assign(reinterpret_cast<const char *>(data), effectiveSize); |
482 | 0 | } |
483 | | |
484 | | static BufferOp staticUpload(QRhiBuffer *buf, quint32 offset, QByteArray data) |
485 | 0 | { |
486 | 0 | BufferOp op = {}; |
487 | 0 | changeToStaticUpload(&op, buf, offset, std::move(data)); |
488 | 0 | return op; |
489 | 0 | } |
490 | | |
491 | | static void changeToStaticUpload(BufferOp *op, QRhiBuffer *buf, quint32 offset, QByteArray data) |
492 | 0 | { |
493 | 0 | op->type = StaticUpload; |
494 | 0 | op->buf = buf; |
495 | 0 | op->offset = offset; |
496 | 0 | op->data.assign(std::move(data)); |
497 | 0 | } |
498 | | |
499 | | static BufferOp read(QRhiBuffer *buf, quint32 offset, quint32 size, QRhiReadbackResult *result) |
500 | 0 | { |
501 | 0 | BufferOp op = {}; |
502 | 0 | op.type = Read; |
503 | 0 | op.buf = buf; |
504 | 0 | op.offset = offset; |
505 | 0 | op.readSize = size; |
506 | 0 | op.result = result; |
507 | 0 | return op; |
508 | 0 | } |
509 | | }; |
510 | | |
511 | | struct TextureOp { |
512 | | enum Type { |
513 | | Upload, |
514 | | Copy, |
515 | | Read, |
516 | | GenMips |
517 | | }; |
518 | | Type type; |
519 | | QRhiTexture *dst; |
520 | | // Specifying multiple uploads for a subresource must be supported. |
521 | | // In the backend this can then end up, where applicable, as a |
522 | | // single, batched copy operation with only one set of barriers. |
523 | | // This helps when doing for example glyph cache fills. |
524 | | using MipLevelUploadList = std::array<QVector<QRhiTextureSubresourceUploadDescription>, QRhi::MAX_MIP_LEVELS>; |
525 | | QVarLengthArray<MipLevelUploadList, 6> subresDesc; |
526 | | QRhiTexture *src; |
527 | | QRhiTextureCopyDescription desc; |
528 | | QRhiReadbackDescription rb; |
529 | | QRhiReadbackResult *result; |
530 | | |
531 | | static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) |
532 | 0 | { |
533 | 0 | TextureOp op = {}; |
534 | 0 | op.type = Upload; |
535 | 0 | op.dst = tex; |
536 | 0 | int maxLayer = -1; |
537 | 0 | for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) { |
538 | 0 | if (it->layer() > maxLayer) |
539 | 0 | maxLayer = it->layer(); |
540 | 0 | } |
541 | 0 | op.subresDesc.resize(maxLayer + 1); |
542 | 0 | for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) |
543 | 0 | op.subresDesc[it->layer()][it->level()].append(it->description()); |
544 | 0 | return op; |
545 | 0 | } |
546 | | |
547 | | static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) |
548 | 0 | { |
549 | 0 | TextureOp op = {}; |
550 | 0 | op.type = Copy; |
551 | 0 | op.dst = dst; |
552 | 0 | op.src = src; |
553 | 0 | op.desc = desc; |
554 | 0 | return op; |
555 | 0 | } |
556 | | |
557 | | static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) |
558 | 0 | { |
559 | 0 | TextureOp op = {}; |
560 | 0 | op.type = Read; |
561 | 0 | op.rb = rb; |
562 | 0 | op.result = result; |
563 | 0 | return op; |
564 | 0 | } |
565 | | |
566 | | static TextureOp genMips(QRhiTexture *tex) |
567 | 0 | { |
568 | 0 | TextureOp op = {}; |
569 | 0 | op.type = GenMips; |
570 | 0 | op.dst = tex; |
571 | 0 | return op; |
572 | 0 | } |
573 | | }; |
574 | | |
575 | | int activeBufferOpCount = 0; // this is the real number of used elements in bufferOps, not bufferOps.count() |
576 | | static const int BUFFER_OPS_STATIC_ALLOC = 64; |
577 | | QVarLengthArray<BufferOp, BUFFER_OPS_STATIC_ALLOC> bufferOps; |
578 | | |
579 | | int activeTextureOpCount = 0; // this is the real number of used elements in textureOps, not textureOps.count() |
580 | | static const int TEXTURE_OPS_STATIC_ALLOC = 32; |
581 | | QVarLengthArray<TextureOp, TEXTURE_OPS_STATIC_ALLOC> textureOps; |
582 | | |
583 | | QRhiResourceUpdateBatch *q = nullptr; |
584 | | QRhiImplementation *rhi = nullptr; |
585 | | int poolIndex = -1; |
586 | | |
587 | | void free(); |
588 | | void merge(QRhiResourceUpdateBatchPrivate *other); |
589 | | bool hasOptimalCapacity() const; |
590 | | void trimOpLists(); |
591 | | |
592 | 0 | static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; } |
593 | | }; |
594 | | |
595 | | template<typename T> |
596 | | struct QRhiBatchedBindings |
597 | | { |
598 | | void feed(int binding, T resource) { // binding must be strictly increasing |
599 | | if (curBinding == -1 || binding > curBinding + 1) { |
600 | | finish(); |
601 | | curBatch.startBinding = binding; |
602 | | curBatch.resources.clear(); |
603 | | curBatch.resources.append(resource); |
604 | | } else { |
605 | | Q_ASSERT(binding == curBinding + 1); |
606 | | curBatch.resources.append(resource); |
607 | | } |
608 | | curBinding = binding; |
609 | | } |
610 | | |
611 | | bool finish() { |
612 | | if (!curBatch.resources.isEmpty()) |
613 | | batches.append(curBatch); |
614 | | return !batches.isEmpty(); |
615 | | } |
616 | | |
617 | | void clear() { |
618 | | batches.clear(); |
619 | | curBatch.resources.clear(); |
620 | | curBinding = -1; |
621 | | } |
622 | | |
623 | | struct Batch { |
624 | | uint startBinding; |
625 | | QVector<T> resources; |
626 | | |
627 | | bool operator==(const Batch &other) const |
628 | | { |
629 | | return startBinding == other.startBinding && resources == other.resources; |
630 | | } |
631 | | |
632 | | bool operator!=(const Batch &other) const |
633 | | { |
634 | | return !operator==(other); |
635 | | } |
636 | | }; |
637 | | |
638 | | // some backends make copies of QRhiBatchedBindings -> implicit sharing and |
639 | | // not having a (possibly wasted) prealloc are beneficial -> use QVector |
640 | | // instead of QVLA |
641 | | QVector<Batch> batches; // sorted by startBinding |
642 | | |
643 | | bool operator==(const QRhiBatchedBindings<T> &other) const |
644 | | { |
645 | | return batches == other.batches; |
646 | | } |
647 | | |
648 | | bool operator!=(const QRhiBatchedBindings<T> &other) const |
649 | | { |
650 | | return !operator==(other); |
651 | | } |
652 | | |
653 | | private: |
654 | | Batch curBatch; |
655 | | int curBinding = -1; |
656 | | }; |
657 | | |
658 | | class QRhiGlobalObjectIdGenerator |
659 | | { |
660 | | public: |
661 | | #ifdef Q_ATOMIC_INT64_IS_SUPPORTED |
662 | | using Type = quint64; |
663 | | #else |
664 | | using Type = quint32; |
665 | | #endif |
666 | | static Type newId(); |
667 | | }; |
668 | | |
669 | | class QRhiPassResourceTracker |
670 | | { |
671 | | public: |
672 | | bool isEmpty() const; |
673 | | void reset(); |
674 | | |
675 | | struct UsageState { |
676 | | int layout; |
677 | | int access; |
678 | | int stage; |
679 | | }; |
680 | | |
681 | | enum BufferStage { |
682 | | BufVertexInputStage, |
683 | | BufVertexStage, |
684 | | BufTCStage, |
685 | | BufTEStage, |
686 | | BufFragmentStage, |
687 | | BufComputeStage, |
688 | | BufGeometryStage |
689 | | }; |
690 | | |
691 | | enum BufferAccess { |
692 | | BufVertexInput, |
693 | | BufIndexRead, |
694 | | BufUniformRead, |
695 | | BufStorageLoad, |
696 | | BufStorageStore, |
697 | | BufStorageLoadStore |
698 | | }; |
699 | | |
700 | | void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage, |
701 | | const UsageState &state); |
702 | | |
703 | | enum TextureStage { |
704 | | TexVertexStage, |
705 | | TexTCStage, |
706 | | TexTEStage, |
707 | | TexFragmentStage, |
708 | | TexColorOutputStage, |
709 | | TexDepthOutputStage, |
710 | | TexComputeStage, |
711 | | TexGeometryStage |
712 | | }; |
713 | | |
714 | | enum TextureAccess { |
715 | | TexSample, |
716 | | TexColorOutput, |
717 | | TexDepthOutput, |
718 | | TexStorageLoad, |
719 | | TexStorageStore, |
720 | | TexStorageLoadStore, |
721 | | TexShadingRate |
722 | | }; |
723 | | |
724 | | void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage, |
725 | | const UsageState &state); |
726 | | |
727 | | struct Buffer { |
728 | | int slot; |
729 | | BufferAccess access; |
730 | | BufferStage stage; |
731 | | UsageState stateAtPassBegin; |
732 | | }; |
733 | | |
734 | 0 | const QVarLengthFlatMap<QRhiBuffer *, Buffer, 12> &buffers() const { return m_buffers; } |
735 | | |
736 | | struct Texture { |
737 | | TextureAccess access; |
738 | | TextureStage stage; |
739 | | UsageState stateAtPassBegin; |
740 | | }; |
741 | | |
742 | 0 | const QVarLengthFlatMap<QRhiTexture *, Texture, 12> &textures() const { return m_textures; } |
743 | | |
744 | | static BufferStage toPassTrackerBufferStage(QRhiShaderResourceBinding::StageFlags stages); |
745 | | static TextureStage toPassTrackerTextureStage(QRhiShaderResourceBinding::StageFlags stages); |
746 | | |
747 | | private: |
748 | | QVarLengthFlatMap<QRhiBuffer *, Buffer, 12> m_buffers; |
749 | | QVarLengthFlatMap<QRhiTexture *, Texture, 12> m_textures; |
750 | | }; |
751 | | |
752 | | Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Buffer, Q_RELOCATABLE_TYPE); |
753 | | Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Texture, Q_RELOCATABLE_TYPE); |
754 | | |
755 | | template<typename T, int GROW = 1024> |
756 | | class QRhiBackendCommandList |
757 | | { |
758 | | public: |
759 | | QRhiBackendCommandList() = default; |
760 | | ~QRhiBackendCommandList() { delete[] v; } |
761 | | inline void reset() { p = 0; } |
762 | | inline bool isEmpty() const { return p == 0; } |
763 | | inline T &get() { |
764 | | if (p == a) { |
765 | | a += GROW; |
766 | | T *nv = new T[a]; |
767 | | if (v) { |
768 | | memcpy(nv, v, p * sizeof(T)); |
769 | | delete[] v; |
770 | | } |
771 | | v = nv; |
772 | | } |
773 | | return v[p++]; |
774 | | } |
775 | | inline void unget() { --p; } |
776 | | inline T *cbegin() const { return v; } |
777 | | inline T *cend() const { return v + p; } |
778 | | inline T *begin() { return v; } |
779 | | inline T *end() { return v + p; } |
780 | | private: |
781 | | Q_DISABLE_COPY(QRhiBackendCommandList) |
782 | | T *v = nullptr; |
783 | | int a = 0; |
784 | | int p = 0; |
785 | | }; |
786 | | |
787 | | struct QRhiRenderTargetAttachmentTracker |
788 | | { |
789 | | struct ResId { quint64 id; uint generation; }; |
790 | | using ResIdList = QVarLengthArray<ResId, 8 * 2 + 1>; // color, resolve, ds |
791 | | |
792 | | template<typename TexType, typename RenderBufferType> |
793 | | static void updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst); |
794 | | |
795 | | template<typename TexType, typename RenderBufferType> |
796 | | static bool isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList); |
797 | | }; |
798 | | |
799 | | inline bool operator==(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) |
800 | 0 | { |
801 | 0 | return a.id == b.id && a.generation == b.generation; |
802 | 0 | } |
803 | | |
804 | | inline bool operator!=(const QRhiRenderTargetAttachmentTracker::ResId &a, const QRhiRenderTargetAttachmentTracker::ResId &b) |
805 | 0 | { |
806 | 0 | return !(a == b); |
807 | 0 | } |
808 | | |
809 | | template<typename TexType, typename RenderBufferType> |
810 | | void QRhiRenderTargetAttachmentTracker::updateResIdList(const QRhiTextureRenderTargetDescription &desc, ResIdList *dst) |
811 | 0 | { |
812 | 0 | const bool hasDepthStencil = desc.depthStencilBuffer() || desc.depthTexture(); |
813 | 0 | dst->resize(desc.colorAttachmentCount() * 2 + (hasDepthStencil ? 1 : 0)); |
814 | 0 | int n = 0; |
815 | 0 | for (auto it = desc.cbeginColorAttachments(), itEnd = desc.cendColorAttachments(); it != itEnd; ++it, ++n) { |
816 | 0 | const QRhiColorAttachment &colorAtt(*it); |
817 | 0 | if (colorAtt.texture()) { |
818 | 0 | TexType *texD = QRHI_RES(TexType, colorAtt.texture()); |
819 | 0 | (*dst)[n] = { texD->globalResourceId(), texD->generation }; |
820 | 0 | } else if (colorAtt.renderBuffer()) { |
821 | 0 | RenderBufferType *rbD = QRHI_RES(RenderBufferType, colorAtt.renderBuffer()); |
822 | 0 | (*dst)[n] = { rbD->globalResourceId(), rbD->generation }; |
823 | 0 | } else { |
824 | 0 | (*dst)[n] = { 0, 0 }; |
825 | 0 | } |
826 | 0 | ++n; |
827 | 0 | if (colorAtt.resolveTexture()) { |
828 | 0 | TexType *texD = QRHI_RES(TexType, colorAtt.resolveTexture()); |
829 | 0 | (*dst)[n] = { texD->globalResourceId(), texD->generation }; |
830 | 0 | } else { |
831 | 0 | (*dst)[n] = { 0, 0 }; |
832 | 0 | } |
833 | 0 | } |
834 | 0 | if (hasDepthStencil) { |
835 | 0 | if (desc.depthTexture()) { |
836 | 0 | TexType *depthTexD = QRHI_RES(TexType, desc.depthTexture()); |
837 | 0 | (*dst)[n] = { depthTexD->globalResourceId(), depthTexD->generation }; |
838 | 0 | } else if (desc.depthStencilBuffer()) { |
839 | 0 | RenderBufferType *depthRbD = QRHI_RES(RenderBufferType, desc.depthStencilBuffer()); |
840 | 0 | (*dst)[n] = { depthRbD->globalResourceId(), depthRbD->generation }; |
841 | 0 | } else { |
842 | 0 | (*dst)[n] = { 0, 0 }; |
843 | 0 | } |
844 | 0 | } |
845 | 0 | } |
846 | | |
847 | | template<typename TexType, typename RenderBufferType> |
848 | | bool QRhiRenderTargetAttachmentTracker::isUpToDate(const QRhiTextureRenderTargetDescription &desc, const ResIdList ¤tResIdList) |
849 | 0 | { |
850 | | // Just as setShaderResources() recognizes if an srb's referenced |
851 | | // resources have been rebuilt (got a create() since the srb's |
852 | | // create()), we should do the same for the textures and renderbuffers |
853 | | // referenced from the rendertarget. It is not uncommon that a texture |
854 | | // or ds buffer gets resized due to following a window size in some |
855 | | // form, which involves a create() on them. It is then nice if the |
856 | | // render target auto-rebuilds in beginPass(). |
857 | |
|
858 | 0 | ResIdList resIdList; |
859 | 0 | updateResIdList<TexType, RenderBufferType>(desc, &resIdList); |
860 | 0 | return resIdList == currentResIdList; |
861 | 0 | } |
862 | | |
863 | | template<typename T> |
864 | | inline T *qrhi_objectFromProxyData(QRhiSwapChainProxyData *pd, QWindow *window, QRhi::Implementation impl, uint objectIndex) |
865 | | { |
866 | | Q_ASSERT(objectIndex < std::size(pd->reserved)); |
867 | | if (!pd->reserved[objectIndex]) // // was not set, no other choice, do it here, whatever thread this is |
868 | | *pd = QRhi::updateSwapChainProxyData(impl, window); |
869 | | return static_cast<T *>(pd->reserved[objectIndex]); |
870 | | } |
871 | | |
872 | | QT_END_NAMESPACE |
873 | | |
874 | | #endif |