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