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