diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-10-10 09:10:26 +0200 |
---|---|---|
committer | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-10-10 09:10:27 +0200 |
commit | 68375b4f3dc9fef5412343ac4496a4af0db43839 (patch) | |
tree | 019e4756c61bba7aead031eed2dc4f4fafd99294 | |
parent | 22891dd897b37be03222ec4881629628fb312442 (diff) | |
parent | 18aa8390ce83b4aa9cabe5609b8f830f86e475e5 (diff) |
Merge remote-tracking branch 'origin/5.14' into 5.15
Change-Id: Iadeca81f499d6b19e86ceae1edd7960db2575e90
39 files changed, 2641 insertions, 510 deletions
diff --git a/qmake/generators/metamakefile.cpp b/qmake/generators/metamakefile.cpp index b8b93bc8cb..7776d77008 100644 --- a/qmake/generators/metamakefile.cpp +++ b/qmake/generators/metamakefile.cpp @@ -141,7 +141,8 @@ bool BuildsMetaMakefileGenerator::write() { Build *glue = nullptr; - if(!makefiles.isEmpty() && !makefiles.first()->build.isNull()) { + if(!makefiles.isEmpty() && !makefiles.first()->build.isNull() + && Option::qmake_mode != Option::QMAKE_GENERATE_PRL) { glue = new Build; glue->name = name; glue->makefile = createMakefileGenerator(project, true); diff --git a/src/concurrent/qtconcurrentmapkernel.h b/src/concurrent/qtconcurrentmapkernel.h index 87fcf30cf9..7c9538a015 100644 --- a/src/concurrent/qtconcurrentmapkernel.h +++ b/src/concurrent/qtconcurrentmapkernel.h @@ -118,16 +118,16 @@ public: return false; } - bool runIterations(Iterator sequenceBeginIterator, int begin, int end, ReducedResultType *) override + bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, ReducedResultType *) override { IntermediateResults<typename MapFunctor::result_type> results; - results.begin = begin; - results.end = end; - results.vector.reserve(end - begin); + results.begin = beginIndex; + results.end = endIndex; + results.vector.reserve(endIndex - beginIndex); Iterator it = sequenceBeginIterator; - std::advance(it, begin); - for (int i = begin; i < end; ++i) { + std::advance(it, beginIndex); + for (int i = beginIndex; i < endIndex; ++i) { results.vector.append(map(*(it))); std::advance(it, 1); } @@ -176,13 +176,13 @@ public: return true; } - bool runIterations(Iterator sequenceBeginIterator, int begin, int end, T *results) override + bool runIterations(Iterator sequenceBeginIterator, int beginIndex, int endIndex, T *results) override { Iterator it = sequenceBeginIterator; - std::advance(it, begin); - for (int i = begin; i < end; ++i) { - runIteration(it, i, results + (i - begin)); + std::advance(it, beginIndex); + for (int i = beginIndex; i < endIndex; ++i) { + runIteration(it, i, results + (i - beginIndex)); std::advance(it, 1); } diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index c8ec8c7410..dabad35688 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -566,7 +566,19 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general") \value TriangleFanTopology Indicates that QRhiGraphicsPipeline::setTopology() supports QRhiGraphicsPipeline::TriangleFan. -*/ + + \value ReadBackNonUniformBuffer Indicates that + \l{QRhiResourceUpdateBatch::readBackBuffer()}{reading buffer contents} is + supported for QRhiBuffer instances with a usage different than + UniformBuffer. While this is supported in the majority of cases, it will be + unsupported with OpenGL ES older than 3.0. + + \value ReadBackNonBaseMipLevel Indicates that specifying a mip level other + than 0 is supported when reading back texture contents. When not supported, + specifying a non-zero level in QRhiReadbackDescription leads to returning + an all-zero image. In practice this feature will be unsupported with OpenGL + ES 2.0, while it will likely be supported everywhere else. + */ /*! \enum QRhi::BeginFrameFlag @@ -3072,9 +3084,13 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) \inmodule QtGui \brief Graphics pipeline state resource. + \note Setting the shader stages is mandatory. There must be at least one + stage, and there must be a vertex stage. + \note Setting the shader resource bindings is mandatory. The referenced QRhiShaderResourceBindings must already be built by the time build() is - called. + called. Associating with a QRhiShaderResourceBindings that has no bindings + is also valid, as long as no shader in any stage expects any resources. \note Setting the render pass descriptor is mandatory. To obtain a QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(), @@ -3083,8 +3099,6 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb) \note Setting the vertex input layout is mandatory. - \note Setting the shader stages is mandatory. - \note sampleCount() defaults to 1 and must match the sample count of the render target's color and depth stencil attachments. @@ -3900,6 +3914,46 @@ quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format, return approxSize; } +bool QRhiImplementation::sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps) +{ + if (ps->cbeginShaderStages() == ps->cendShaderStages()) { + qWarning("Cannot build a graphics pipeline without any stages"); + return false; + } + + bool hasVertexStage = false; + for (auto it = ps->cbeginShaderStages(), itEnd = ps->cendShaderStages(); it != itEnd; ++it) { + if (!it->shader().isValid()) { + qWarning("Empty shader passed to graphics pipeline"); + return false; + } + if (it->type() == QRhiShaderStage::Vertex) { + hasVertexStage = true; + const QRhiVertexInputLayout inputLayout = ps->vertexInputLayout(); + if (inputLayout.cbeginAttributes() == inputLayout.cendAttributes()) { + qWarning("Vertex stage present without any vertex inputs"); + return false; + } + } + } + if (!hasVertexStage) { + qWarning("Cannot build a graphics pipeline without a vertex stage"); + return false; + } + + if (!ps->renderPassDescriptor()) { + qWarning("Cannot build a graphics pipeline without a QRhiRenderPassDescriptor"); + return false; + } + + if (!ps->shaderResourceBindings()) { + qWarning("Cannot build a graphics pipeline without QRhiShaderResourceBindings"); + return false; + } + + return true; +} + /*! \internal */ @@ -4175,7 +4229,7 @@ void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other) void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data) { if (size > 0) - d->dynamicBufferUpdates.append({ buf, offset, size, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(buf, offset, size, data)); } /*! @@ -4189,7 +4243,7 @@ void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, i void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data) { if (size > 0) - d->staticBufferUploads.append({ buf, offset, size, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, offset, size, data)); } /*! @@ -4199,7 +4253,28 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, in void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *data) { if (buf->size() > 0) - d->staticBufferUploads.append({ buf, 0, 0, data }); + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::staticUpload(buf, 0, 0, data)); +} + +/*! + Enqueues reading back a region of the QRhiBuffer \a buf. The size of the + region is specified by \a size in bytes, \a offset is the offset in bytes + to start reading from. + + A readback is asynchronous. \a result contains a callback that is invoked + when the operation has completed. The data is provided in + QRhiBufferReadbackResult::data. Upon successful completion that QByteArray + will have a size equal to \a size. On failure the QByteArray will be empty. + + \note Reading buffers with a usage different than QRhiBuffer::UniformBuffer + is supported only when the QRhi::ReadBackNonUniformBuffer feature is + reported as supported. + + \a readBackTexture(), QRhi::isFeatureSupported() + */ +void QRhiResourceUpdateBatch::readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result) +{ + d->bufferOps.append(QRhiResourceUpdateBatchPrivate::BufferOp::read(buf, offset, size, result)); } /*! @@ -4212,7 +4287,7 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *da void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) { if (desc.cbeginEntries() != desc.cendEntries()) - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureUpload(tex, desc)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::upload(tex, desc)); } /*! @@ -4237,7 +4312,7 @@ void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QImage &imag */ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureCopy(dst, src, desc)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::copy(dst, src, desc)); } /*! @@ -4289,7 +4364,7 @@ void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, co */ void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureRead(rb, result)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::read(rb, result)); } /*! @@ -4301,7 +4376,7 @@ void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, */ void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer) { - d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureMipGen(tex, layer)); + d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::genMips(tex, layer)); } /*! @@ -4350,8 +4425,7 @@ void QRhiResourceUpdateBatchPrivate::free() { Q_ASSERT(poolIndex >= 0 && rhi->resUpdPool[poolIndex] == q); - dynamicBufferUpdates.clear(); - staticBufferUploads.clear(); + bufferOps.clear(); textureOps.clear(); rhi->resUpdPoolMap.clearBit(poolIndex); @@ -4360,9 +4434,13 @@ void QRhiResourceUpdateBatchPrivate::free() void QRhiResourceUpdateBatchPrivate::merge(QRhiResourceUpdateBatchPrivate *other) { - dynamicBufferUpdates += other->dynamicBufferUpdates; - staticBufferUploads += other->staticBufferUploads; - textureOps += other->textureOps; + bufferOps.reserve(bufferOps.size() + other->bufferOps.size()); + for (const BufferOp &op : qAsConst(other->bufferOps)) + bufferOps.append(op); + + textureOps.reserve(textureOps.size() + other->textureOps.size()); + for (const TextureOp &op : qAsConst(other->textureOps)) + textureOps.append(op); } /*! diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h index 5b371af727..907924c788 100644 --- a/src/gui/rhi/qrhi_p.h +++ b/src/gui/rhi/qrhi_p.h @@ -72,7 +72,6 @@ class QRhiCommandBuffer; class QRhiResourceUpdateBatch; class QRhiResourceUpdateBatchPrivate; class QRhiProfiler; -class QRhiShaderResourceBindingPrivate; class Q_GUI_EXPORT QRhiDepthStencilClearValue { @@ -1355,6 +1354,12 @@ struct Q_GUI_EXPORT QRhiReadbackResult QByteArray data; }; // non-movable due to the std::function +struct Q_GUI_EXPORT QRhiBufferReadbackResult +{ + std::function<void()> completed = nullptr; + QByteArray data; +}; + class Q_GUI_EXPORT QRhiResourceUpdateBatch { public: @@ -1367,6 +1372,7 @@ public: void updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data); void uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data); void uploadStaticBuffer(QRhiBuffer *buf, const void *data); + void readBackBuffer(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result); void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc); void uploadTexture(QRhiTexture *tex, const QImage &image); void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription()); @@ -1428,7 +1434,9 @@ public: VertexShaderPointSize, BaseVertex, BaseInstance, - TriangleFanTopology + TriangleFanTopology, + ReadBackNonUniformBuffer, + ReadBackNonBaseMipLevel }; enum BeginFrameFlag { diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h index 80a95e2fcc..baffe28202 100644 --- a/src/gui/rhi/qrhi_p_p.h +++ b/src/gui/rhi/qrhi_p_p.h @@ -205,6 +205,8 @@ public: cleanupCallbacks.append(callback); } + bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps); + QRhi *q; static const int MAX_SHADER_CACHE_ENTRIES = 128; @@ -218,7 +220,7 @@ private: QRhi::Implementation implType; QThread *implThread; QRhiProfiler profiler; - QVector<QRhiResourceUpdateBatch *> resUpdPool; + QVarLengthArray<QRhiResourceUpdateBatch *, 4> resUpdPool; QBitArray resUpdPoolMap; QSet<QRhiResource *> resources; QSet<QRhiResource *> pendingReleaseAndDestroyResources; @@ -271,26 +273,49 @@ bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array<T, class QRhiResourceUpdateBatchPrivate { public: - struct DynamicBufferUpdate { - DynamicBufferUpdate() { } - DynamicBufferUpdate(QRhiBuffer *buf_, int offset_, int size_, const void *data_) - : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_) - { } - - QRhiBuffer *buf = nullptr; - int offset = 0; + struct BufferOp { + enum Type { + DynamicUpdate, + StaticUpload, + Read + }; + Type type; + QRhiBuffer *buf; + int offset; QByteArray data; - }; + int readSize; + QRhiBufferReadbackResult *result; - struct StaticBufferUpload { - StaticBufferUpload() { } - StaticBufferUpload(QRhiBuffer *buf_, int offset_, int size_, const void *data_) - : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_ ? size_ : buf_->size()) - { } + static BufferOp dynamicUpdate(QRhiBuffer *buf, int offset, int size, const void *data) + { + BufferOp op; + op.type = DynamicUpdate; + op.buf = buf; + op.offset = offset; + op.data = QByteArray(reinterpret_cast<const char *>(data), size ? size : buf->size()); + return op; + } - QRhiBuffer *buf = nullptr; - int offset = 0; - QByteArray data; + static BufferOp staticUpload(QRhiBuffer *buf, int offset, int size, const void *data) + { + BufferOp op; + op.type = StaticUpload; + op.buf = buf; + op.offset = offset; + op.data = QByteArray(reinterpret_cast<const char *>(data), size ? size : buf->size()); + return op; + } + + static BufferOp read(QRhiBuffer *buf, int offset, int size, QRhiBufferReadbackResult *result) + { + BufferOp op; + op.type = Read; + op.buf = buf; + op.offset = offset; + op.readSize = size; + op.result = result; + return op; + } }; struct TextureOp { @@ -298,73 +323,62 @@ public: Upload, Copy, Read, - MipGen + GenMips }; Type type; - struct SUpload { - QRhiTexture *tex = nullptr; - // Specifying multiple uploads for a subresource must be supported. - // In the backend this can then end up, where applicable, as a - // single, batched copy operation with only one set of barriers. - // This helps when doing for example glyph cache fills. - QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; - } upload; - struct SCopy { - QRhiTexture *dst = nullptr; - QRhiTexture *src = nullptr; - QRhiTextureCopyDescription desc; - } copy; - struct SRead { - QRhiReadbackDescription rb; - QRhiReadbackResult *result; - } read; - struct SMipGen { - QRhiTexture *tex = nullptr; - int layer = 0; - } mipgen; - - static TextureOp textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) + QRhiTexture *dst; + // Specifying multiple uploads for a subresource must be supported. + // In the backend this can then end up, where applicable, as a + // single, batched copy operation with only one set of barriers. + // This helps when doing for example glyph cache fills. + QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; + QRhiTexture *src; + QRhiTextureCopyDescription desc; + QRhiReadbackDescription rb; + QRhiReadbackResult *result; + int layer; + + static TextureOp upload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc) { TextureOp op; op.type = Upload; - op.upload.tex = tex; + op.dst = tex; for (auto it = desc.cbeginEntries(), itEnd = desc.cendEntries(); it != itEnd; ++it) - op.upload.subresDesc[it->layer()][it->level()].append(it->description()); + op.subresDesc[it->layer()][it->level()].append(it->description()); return op; } - static TextureOp textureCopy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) + static TextureOp copy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc) { TextureOp op; op.type = Copy; - op.copy.dst = dst; - op.copy.src = src; - op.copy.desc = desc; + op.dst = dst; + op.src = src; + op.desc = desc; return op; } - static TextureOp textureRead(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) + static TextureOp read(const QRhiReadbackDescription &rb, QRhiReadbackResult *result) { TextureOp op; op.type = Read; - op.read.rb = rb; - op.read.result = result; + op.rb = rb; + op.result = result; return op; } - static TextureOp textureMipGen(QRhiTexture *tex, int layer) + static TextureOp genMips(QRhiTexture *tex, int layer) { TextureOp op; - op.type = MipGen; - op.mipgen.tex = tex; - op.mipgen.layer = layer; + op.type = GenMips; + op.dst = tex; + op.layer = layer; return op; } }; - QVector<DynamicBufferUpdate> dynamicBufferUpdates; - QVector<StaticBufferUpload> staticBufferUploads; - QVector<TextureOp> textureOps; + QVarLengthArray<BufferOp, 1024> bufferOps; + QVarLengthArray<TextureOp, 256> textureOps; QRhiResourceUpdateBatch *q = nullptr; QRhiImplementation *rhi = nullptr; @@ -376,8 +390,7 @@ public: static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; } }; -Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::StaticBufferUpload, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::BufferOp, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::TextureOp, Q_MOVABLE_TYPE); template<typename T> diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index f011d68ada..717f3e6d6c 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -471,6 +471,10 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return false; + case QRhi::ReadBackNonUniformBuffer: + return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; @@ -1323,61 +1327,105 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); - bufD->hasPendingDynamicUpdates = true; - } - - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - QD3D11CommandBuffer::Command cmd; - cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes; - cmd.args.updateSubRes.dst = bufD->buffer; - cmd.args.updateSubRes.dstSubRes = 0; - cmd.args.updateSubRes.src = cbD->retainData(u.data); - cmd.args.updateSubRes.srcRowPitch = 0; - // Specify the region (even when offset is 0 and all data is provided) - // since the ID3D11Buffer's size is rounded up to be a multiple of 256 - // while the data we have has the original size. - D3D11_BOX box; - box.left = UINT(u.offset); - box.top = box.front = 0; - box.back = box.bottom = 1; - box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc - cmd.args.updateSubRes.hasDstBox = true; - cmd.args.updateSubRes.dstBox = box; - cbD->commands.append(cmd); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); + bufD->hasPendingDynamicUpdates = true; + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes; + cmd.args.updateSubRes.dst = bufD->buffer; + cmd.args.updateSubRes.dstSubRes = 0; + cmd.args.updateSubRes.src = cbD->retainData(u.data); + cmd.args.updateSubRes.srcRowPitch = 0; + // Specify the region (even when offset is 0 and all data is provided) + // since the ID3D11Buffer's size is rounded up to be a multiple of 256 + // while the data we have has the original size. + D3D11_BOX box; + box.left = UINT(u.offset); + box.top = box.front = 0; + box.back = box.bottom = 1; + box.right = UINT(u.offset + u.data.size()); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc + cmd.args.updateSubRes.hasDstBox = true; + cmd.args.updateSubRes.dstBox = box; + cbD->commands.append(cmd); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf); + if (bufD->m_type == QRhiBuffer::Dynamic) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), bufD->dynBuf.constData() + u.offset, size_t(u.readSize)); + } else { + BufferReadback readback; + readback.result = u.result; + readback.byteSize = u.readSize; + + D3D11_BUFFER_DESC desc; + memset(&desc, 0, sizeof(desc)); + desc.ByteWidth = readback.byteSize; + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + HRESULT hr = dev->CreateBuffer(&desc, nullptr, &readback.stagingBuf); + if (FAILED(hr)) { + qWarning("Failed to create buffer: %s", qPrintable(comErrorMessage(hr))); + continue; + } + QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.stagingBuf)), bufD, readback.byteSize)); + + QD3D11CommandBuffer::Command cmd; + cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; + cmd.args.copySubRes.dst = readback.stagingBuf; + cmd.args.copySubRes.dstSubRes = 0; + cmd.args.copySubRes.dstX = 0; + cmd.args.copySubRes.dstY = 0; + cmd.args.copySubRes.src = bufD->buffer; + cmd.args.copySubRes.srcSubRes = 0; + cmd.args.copySubRes.hasSrcBox = true; + D3D11_BOX box; + box.left = UINT(u.offset); + box.top = box.front = 0; + box.back = box.bottom = 1; + box.right = UINT(u.offset + u.readSize); + cmd.args.copySubRes.srcBox = box; + cbD->commands.append(cmd); + + activeBufferReadbacks.append(readback); + } + if (u.result->completed) + u.result->completed(); + } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.upload.tex); + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.dst); for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.copy.src); - QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.copy.dst); - UINT srcSubRes = D3D11CalcSubresource(UINT(u.copy.desc.sourceLevel()), UINT(u.copy.desc.sourceLayer()), srcD->mipLevelCount); - UINT dstSubRes = D3D11CalcSubresource(UINT(u.copy.desc.destinationLevel()), UINT(u.copy.desc.destinationLayer()), dstD->mipLevelCount); - const QPoint dp = u.copy.desc.destinationTopLeft(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); - const QPoint sp = u.copy.desc.sourceTopLeft(); + Q_ASSERT(u.src && u.dst); + QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.src); + QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.dst); + UINT srcSubRes = D3D11CalcSubresource(UINT(u.desc.sourceLevel()), UINT(u.desc.sourceLayer()), srcD->mipLevelCount); + UINT dstSubRes = D3D11CalcSubresource(UINT(u.desc.destinationLevel()), UINT(u.desc.destinationLayer()), dstD->mipLevelCount); + const QPoint dp = u.desc.destinationTopLeft(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); + const QPoint sp = u.desc.sourceTopLeft(); D3D11_BOX srcBox; srcBox.left = UINT(sp.x()); srcBox.top = UINT(sp.y()); srcBox.front = 0; // back, right, bottom are exclusive - srcBox.right = srcBox.left + UINT(size.width()); - srcBox.bottom = srcBox.top + UINT(size.height()); + srcBox.right = srcBox.left + UINT(copySize.width()); + srcBox.bottom = srcBox.top + UINT(copySize.height()); srcBox.back = 1; QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; @@ -1391,16 +1439,16 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copySubRes.srcBox = srcBox; cbD->commands.append(cmd); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - ActiveReadback aRb; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + TextureReadback readback; + readback.desc = u.rb; + readback.result = u.result; ID3D11Resource *src; DXGI_FORMAT dxgiFormat; QSize pixelSize; QRhiTexture::Format format; UINT subres = 0; - QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.read.rb.texture()); + QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture()); QD3D11SwapChain *swapChainD = nullptr; if (texD) { @@ -1410,9 +1458,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } src = texD->tex; dxgiFormat = texD->dxgiFormat; - pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) : texD->m_pixelSize; + pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); format = texD->m_format; - subres = D3D11CalcSubresource(UINT(u.read.rb.level()), UINT(u.read.rb.layer()), texD->mipLevelCount); + subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(u.rb.layer()), texD->mipLevelCount); } else { Q_ASSERT(contextState.currentSwapChain); swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain); @@ -1435,9 +1483,9 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate if (format == QRhiTexture::UnknownFormat) continue; } - quint32 bufSize = 0; + quint32 byteSize = 0; quint32 bpl = 0; - textureFormatInfo(format, pixelSize, &bpl, &bufSize); + textureFormatInfo(format, pixelSize, &bpl, &byteSize); D3D11_TEXTURE2D_DESC desc; memset(&desc, 0, sizeof(desc)); @@ -1457,7 +1505,7 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(stagingTex)), texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD), - bufSize)); + byteSize)); QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes; @@ -1470,18 +1518,18 @@ void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate cmd.args.copySubRes.hasSrcBox = false; cbD->commands.append(cmd); - aRb.stagingTex = stagingTex; - aRb.bufSize = bufSize; - aRb.bpl = bpl; - aRb.pixelSize = pixelSize; - aRb.format = format; + readback.stagingTex = stagingTex; + readback.byteSize = byteSize; + readback.bpl = bpl; + readback.pixelSize = pixelSize; + readback.format = format; - activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - Q_ASSERT(u.mipgen.tex->flags().testFlag(QRhiTexture::UsedWithGenerateMips)); + activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + Q_ASSERT(u.dst->flags().testFlag(QRhiTexture::UsedWithGenerateMips)); QD3D11CommandBuffer::Command cmd; cmd.cmd = QD3D11CommandBuffer::Command::GenMip; - cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.mipgen.tex)->srv; + cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.dst)->srv; cbD->commands.append(cmd); } } @@ -1494,37 +1542,58 @@ void QRhiD3D11::finishActiveReadbacks() QVarLengthArray<std::function<void()>, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiD3D11::ActiveReadback &aRb(activeReadbacks[i]); - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); + for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiD3D11::TextureReadback &readback(activeTextureReadbacks[i]); + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; D3D11_MAPPED_SUBRESOURCE mp; - HRESULT hr = context->Map(aRb.stagingTex, 0, D3D11_MAP_READ, 0, &mp); - if (FAILED(hr)) { + HRESULT hr = context->Map(readback.stagingTex, 0, D3D11_MAP_READ, 0, &mp); + if (SUCCEEDED(hr)) { + readback.result->data.resize(int(readback.byteSize)); + // nothing says the rows are tightly packed in the texture, must take + // the stride into account + char *dst = readback.result->data.data(); + char *src = static_cast<char *>(mp.pData); + for (int y = 0, h = readback.pixelSize.height(); y != h; ++y) { + memcpy(dst, src, readback.bpl); + dst += readback.bpl; + src += mp.RowPitch; + } + context->Unmap(readback.stagingTex, 0); + } else { qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr))); - aRb.stagingTex->Release(); - continue; } - // nothing says the rows are tightly packed in the texture, must take - // the stride into account - char *dst = aRb.result->data.data(); - char *src = static_cast<char *>(mp.pData); - for (int y = 0, h = aRb.pixelSize.height(); y != h; ++y) { - memcpy(dst, src, aRb.bpl); - dst += aRb.bpl; - src += mp.RowPitch; + + readback.stagingTex->Release(); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingTex)))); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + activeTextureReadbacks.removeAt(i); + } + + for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { + const QRhiD3D11::BufferReadback &readback(activeBufferReadbacks[i]); + + D3D11_MAPPED_SUBRESOURCE mp; + HRESULT hr = context->Map(readback.stagingBuf, 0, D3D11_MAP_READ, 0, &mp); + if (SUCCEEDED(hr)) { + readback.result->data.resize(int(readback.byteSize)); + memcpy(readback.result->data.data(), mp.pData, readback.byteSize); + context->Unmap(readback.stagingBuf, 0); + } else { + qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr))); } - context->Unmap(aRb.stagingTex, 0); - aRb.stagingTex->Release(); - QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.stagingTex)))); + readback.stagingBuf->Release(); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.stagingBuf)))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - activeReadbacks.removeAt(i); + activeBufferReadbacks.removeAt(i); } for (auto f : completedCallbacks) @@ -3423,6 +3492,8 @@ bool QD3D11GraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiD3D11); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; D3D11_RASTERIZER_DESC rastDesc; memset(&rastDesc, 0, sizeof(rastDesc)); diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h index 6699e7ad64..26de34ae0a 100644 --- a/src/gui/rhi/qrhid3d11_p_p.h +++ b/src/gui/rhi/qrhid3d11_p_p.h @@ -678,16 +678,22 @@ public: QD3D11CommandBuffer cbWrapper; } ofr; - struct ActiveReadback { + struct TextureReadback { QRhiReadbackDescription desc; QRhiReadbackResult *result; ID3D11Texture2D *stagingTex; - quint32 bufSize; + quint32 byteSize; quint32 bpl; QSize pixelSize; QRhiTexture::Format format; }; - QVector<ActiveReadback> activeReadbacks; + QVector<TextureReadback> activeTextureReadbacks; + struct BufferReadback { + QRhiBufferReadbackResult *result; + quint32 byteSize; + ID3D11Buffer *stagingBuf; + }; + QVector<BufferReadback> activeBufferReadbacks; struct Shader { Shader() = default; @@ -711,7 +717,8 @@ public: } deviceCurse; }; -Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_MOVABLE_TYPE); QT_END_NAMESPACE diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 277cf12fd2..e355979626 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -276,6 +276,10 @@ QT_BEGIN_NAMESPACE #define GL_POINT_SPRITE 0x8861 #endif +#ifndef GL_MAP_READ_BIT +#define GL_MAP_READ_BIT 0x0001 +#endif + Q_DECLARE_LOGGING_CATEGORY(lcOpenGLProgramDiskCache) /*! @@ -492,6 +496,15 @@ bool QRhiGles2::create(QRhi::Flags flags) else caps.textureCompareMode = true; + // proper as in ES 3.0 (glMapBufferRange), not the old glMapBuffer + // extension(s) (which is not in ES 3.0...messy) + caps.properMapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBufferRange); + + if (caps.gles) + caps.nonBaseLevelFramebufferTexture = caps.ctxMajor >= 3; // ES 3.0 + else + caps.nonBaseLevelFramebufferTexture = true; + if (!caps.gles) { f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); f->glEnable(GL_POINT_SPRITE); @@ -734,6 +747,10 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const return false; // not in ES 3.2, so won't bother case QRhi::TriangleFanTopology: return true; + case QRhi::ReadBackNonUniformBuffer: + return !caps.gles || caps.properMapBuffer; + case QRhi::ReadBackNonBaseMipLevel: + return caps.nonBaseLevelFramebufferTexture; default: Q_UNREACHABLE(); return false; @@ -1417,65 +1434,83 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { - memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); - } else { - trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); - QGles2CommandBuffer::Command cmd; - cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; - cmd.args.bufferSubData.target = bufD->targetForDataOps; - cmd.args.bufferSubData.buffer = bufD->buffer; - cmd.args.bufferSubData.offset = u.offset; - cmd.args.bufferSubData.size = u.data.size(); - cmd.args.bufferSubData.data = cbD->retainData(u.data); - cbD->commands.append(cmd); - } - } - - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { - memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); - } else { - trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); - QGles2CommandBuffer::Command cmd; - cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; - cmd.args.bufferSubData.target = bufD->targetForDataOps; - cmd.args.bufferSubData.buffer = bufD->buffer; - cmd.args.bufferSubData.offset = u.offset; - cmd.args.bufferSubData.size = u.data.size(); - cmd.args.bufferSubData.data = cbD->retainData(u.data); - cbD->commands.append(cmd); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { + memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else { + trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; + cmd.args.bufferSubData.target = bufD->targetForDataOps; + cmd.args.bufferSubData.buffer = bufD->buffer; + cmd.args.bufferSubData.offset = u.offset; + cmd.args.bufferSubData.size = u.data.size(); + cmd.args.bufferSubData.data = cbD->retainData(u.data); + cbD->commands.append(cmd); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { + memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else { + trackedBufferBarrier(cbD, bufD, QGles2Buffer::AccessUpdate); + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::BufferSubData; + cmd.args.bufferSubData.target = bufD->targetForDataOps; + cmd.args.bufferSubData.buffer = bufD->buffer; + cmd.args.bufferSubData.offset = u.offset; + cmd.args.bufferSubData.size = u.data.size(); + cmd.args.bufferSubData.data = cbD->retainData(u.data); + cbD->commands.append(cmd); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf); + if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), bufD->ubuf.constData() + u.offset, size_t(u.readSize)); + if (u.result->completed) + u.result->completed(); + } else { + QGles2CommandBuffer::Command cmd; + cmd.cmd = QGles2CommandBuffer::Command::GetBufferSubData; + cmd.args.getBufferSubData.result = u.result; + cmd.args.getBufferSubData.target = bufD->targetForDataOps; + cmd.args.getBufferSubData.buffer = bufD->buffer; + cmd.args.getBufferSubData.offset = u.offset; + cmd.args.getBufferSubData.size = u.readSize; + cbD->commands.append(cmd); + } } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.upload.tex); + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(texD, cbD, layer, level, subresDesc); } } texD->specified = true; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.copy.src); - QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.copy.dst); + Q_ASSERT(u.src && u.dst); + QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.src); + QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, srcD, QGles2Texture::AccessRead); trackedImageBarrier(cbD, dstD, QGles2Texture::AccessUpdate); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); // do not translate coordinates, even if sp is bottom-left from gl's pov - const QPoint sp = u.copy.desc.sourceTopLeft(); - const QPoint dp = u.copy.desc.destinationTopLeft(); + const QPoint sp = u.desc.sourceTopLeft(); + const QPoint dp = u.desc.destinationTopLeft(); const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target; @@ -1485,43 +1520,44 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::CopyTex; - cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.copy.desc.sourceLayer()); + cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + uint(u.desc.sourceLayer()); cmd.args.copyTex.srcTexture = srcD->texture; - cmd.args.copyTex.srcLevel = u.copy.desc.sourceLevel(); + cmd.args.copyTex.srcLevel = u.desc.sourceLevel(); cmd.args.copyTex.srcX = sp.x(); cmd.args.copyTex.srcY = sp.y(); cmd.args.copyTex.dstTarget = dstD->target; cmd.args.copyTex.dstTexture = dstD->texture; - cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.copy.desc.destinationLayer()); - cmd.args.copyTex.dstLevel = u.copy.desc.destinationLevel(); + cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + uint(u.desc.destinationLayer()); + cmd.args.copyTex.dstLevel = u.desc.destinationLevel(); cmd.args.copyTex.dstX = dp.x(); cmd.args.copyTex.dstY = dp.y(); - cmd.args.copyTex.w = size.width(); - cmd.args.copyTex.h = size.height(); + cmd.args.copyTex.w = copySize.width(); + cmd.args.copyTex.h = copySize.height(); cbD->commands.append(cmd); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::ReadPixels; - cmd.args.readPixels.result = u.read.result; - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.read.rb.texture()); + cmd.args.readPixels.result = u.result; + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.rb.texture()); if (texD) trackedImageBarrier(cbD, texD, QGles2Texture::AccessRead); cmd.args.readPixels.texture = texD ? texD->texture : 0; if (texD) { - cmd.args.readPixels.w = texD->m_pixelSize.width(); - cmd.args.readPixels.h = texD->m_pixelSize.height(); + const QSize readImageSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); + cmd.args.readPixels.w = readImageSize.width(); + cmd.args.readPixels.h = readImageSize.height(); cmd.args.readPixels.format = texD->m_format; const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target; - cmd.args.readPixels.readTarget = faceTargetBase + uint(u.read.rb.layer()); - cmd.args.readPixels.level = u.read.rb.level(); + cmd.args.readPixels.readTarget = faceTargetBase + uint(u.rb.layer()); + cmd.args.readPixels.level = u.rb.level(); } cbD->commands.append(cmd); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QGles2Texture *texD = QRHI_RES(QGles2Texture, u.mipgen.tex); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QGles2Texture *texD = QRHI_RES(QGles2Texture, u.dst); trackedImageBarrier(cbD, texD, QGles2Texture::AccessFramebuffer); QGles2CommandBuffer::Command cmd; cmd.cmd = QGles2CommandBuffer::Command::GenMip; @@ -2080,6 +2116,33 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size, cmd.args.bufferSubData.data); break; + case QGles2CommandBuffer::Command::GetBufferSubData: + { + QRhiBufferReadbackResult *result = cmd.args.getBufferSubData.result; + f->glBindBuffer(cmd.args.getBufferSubData.target, cmd.args.getBufferSubData.buffer); + if (caps.gles) { + if (caps.properMapBuffer) { + void *p = f->glMapBufferRange(cmd.args.getBufferSubData.target, + cmd.args.getBufferSubData.offset, + cmd.args.getBufferSubData.size, + GL_MAP_READ_BIT); + if (p) { + result->data.resize(cmd.args.getBufferSubData.size); + memcpy(result->data.data(), p, size_t(cmd.args.getBufferSubData.size)); + f->glUnmapBuffer(cmd.args.getBufferSubData.target); + } + } + } else { + result->data.resize(cmd.args.getBufferSubData.size); + f->glGetBufferSubData(cmd.args.getBufferSubData.target, + cmd.args.getBufferSubData.offset, + cmd.args.getBufferSubData.size, + result->data.data()); + } + if (result->completed) + result->completed(); + } + break; case QGles2CommandBuffer::Command::CopyTex: { GLuint fbo; @@ -2101,23 +2164,31 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) QRhiReadbackResult *result = cmd.args.readPixels.result; GLuint tex = cmd.args.readPixels.texture; GLuint fbo = 0; + int mipLevel = 0; if (tex) { result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h); result->format = cmd.args.readPixels.format; - f->glGenFramebuffers(1, &fbo); - f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); - f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level); + mipLevel = cmd.args.readPixels.level; + if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { + f->glGenFramebuffers(1, &fbo); + f->glBindFramebuffer(GL_FRAMEBUFFER, fbo); + f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, mipLevel); + } } else { result->pixelSize = currentSwapChain->pixelSize; result->format = QRhiTexture::RGBA8; // readPixels handles multisample resolving implicitly } result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4); - // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it. - f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(), - GL_RGBA, GL_UNSIGNED_BYTE, - result->data.data()); + if (mipLevel == 0 || caps.nonBaseLevelFramebufferTexture) { + // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it. + f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(), + GL_RGBA, GL_UNSIGNED_BYTE, + result->data.data()); + } else { + result->data.fill('\0'); + } if (fbo) { f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); f->glDeleteFramebuffers(1, &fbo); @@ -3620,6 +3691,9 @@ bool QGles2GraphicsPipeline::build() if (!rhiD->ensureContext()) return false; + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + drawMode = toGlTopology(m_topology); program = rhiD->f->glCreateProgram(); diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h index 646836a699..8814d9c19d 100644 --- a/src/gui/rhi/qrhigles2_p_p.h +++ b/src/gui/rhi/qrhigles2_p_p.h @@ -312,8 +312,8 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer BindShaderResources, BindFramebuffer, Clear, - BufferData, BufferSubData, + GetBufferSubData, CopyTex, ReadPixels, SubImage, @@ -402,6 +402,13 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer const void *data; // must come from retainData() } bufferSubData; struct { + QRhiBufferReadbackResult *result; + GLenum target; + GLuint buffer; + int offset; + int size; + } getBufferSubData; + struct { GLenum srcFaceTarget; GLuint srcTexture; int srcLevel; @@ -744,7 +751,10 @@ public: rgba8Format(false), instancing(false), baseVertex(false), - compute(false) + compute(false), + textureCompareMode(false), + properMapBuffer(false), + nonBaseLevelFramebufferTexture(false) { } int ctxMajor; int ctxMinor; @@ -775,6 +785,8 @@ public: uint baseVertex : 1; uint compute : 1; uint textureCompareMode : 1; + uint properMapBuffer : 1; + uint nonBaseLevelFramebufferTexture : 1; } caps; QGles2SwapChain *currentSwapChain = nullptr; QVector<GLint> supportedCompressedFormats; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index f9d9cdc01a..5f14d917b8 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -205,7 +205,7 @@ struct QRhiMetalData QMetalCommandBuffer cbWrapper; } ofr; - struct ActiveReadback { + struct TextureReadback { int activeFrameSlot = -1; QRhiReadbackDescription desc; QRhiReadbackResult *result; @@ -214,7 +214,7 @@ struct QRhiMetalData QSize pixelSize; QRhiTexture::Format format; }; - QVector<ActiveReadback> activeReadbacks; + QVector<TextureReadback> activeTextureReadbacks; API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr; API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil; @@ -225,14 +225,14 @@ struct QRhiMetalData }; Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiMetalData::TextureReadback, Q_MOVABLE_TYPE); struct QMetalBufferData { bool managed; bool slotted; id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT]; - QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT]; + QVarLengthArray<QRhiResourceUpdateBatchPrivate::BufferOp, 16> pendingUpdates[QMTL_FRAMES_IN_FLIGHT]; }; struct QMetalRenderBufferData @@ -367,6 +367,11 @@ bool QRhiMetal::create(QRhi::Flags flags) else d->dev = MTLCreateSystemDefaultDevice(); + if (!d->dev) { + qWarning("No MTLDevice"); + return false; + } + qCDebug(QRHI_LOG_INFO, "Metal device: %s", qPrintable(QString::fromNSString([d->dev name]))); if (importedCmdQueue) @@ -552,6 +557,10 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return false; + case QRhi::ReadBackNonUniformBuffer: + return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; @@ -1541,21 +1550,33 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) - bufD->d->pendingUpdates[i].append(u); - } - - // Due to the Metal API the handling of static and dynamic buffers is - // basically the same. So go through the same pendingUpdates machinery. - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); - for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) - bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() }); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) + bufD->d->pendingUpdates[i].append(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + // Due to the Metal API the handling of static and dynamic buffers is + // basically the same. So go through the same pendingUpdates machinery. + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i) + bufD->d->pendingUpdates[i].append( + QRhiResourceUpdateBatchPrivate::BufferOp::dynamicUpdate(u.buf, u.offset, u.data.size(), u.data.constData())); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf); + executeBufferHostWritesForCurrentFrame(bufD); + const int idx = bufD->d->slotted ? currentFrameSlot : 0; + char *p = reinterpret_cast<char *>([bufD->d->buf[idx] contents]); + if (p) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), p + u.offset, size_t(u.readSize)); + } + if (u.result->completed) + u.result->completed(); + } } id<MTLBlitCommandEncoder> blitEnc = nil; @@ -1569,11 +1590,11 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex); + QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst); qsizetype stagingSize = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } @@ -1588,7 +1609,7 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate qsizetype curOfs = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs); } } @@ -1603,32 +1624,33 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate d->releaseQueue.append(e); QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot)); } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src); - QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst); - const QPoint dp = u.copy.desc.destinationTopLeft(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); - const QPoint sp = u.copy.desc.sourceTopLeft(); + Q_ASSERT(u.src && u.dst); + QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.src); + QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.dst); + const QPoint dp = u.desc.destinationTopLeft(); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); + const QPoint sp = u.desc.sourceTopLeft(); ensureBlit(); [blitEnc copyFromTexture: srcD->d->tex - sourceSlice: NSUInteger(u.copy.desc.sourceLayer()) - sourceLevel: NSUInteger(u.copy.desc.sourceLevel()) + sourceSlice: NSUInteger(u.desc.sourceLayer()) + sourceLevel: NSUInteger(u.desc.sourceLevel()) sourceOrigin: MTLOriginMake(NSUInteger(sp.x()), NSUInteger(sp.y()), 0) - sourceSize: MTLSizeMake(NSUInteger(size.width()), NSUInteger(size.height()), 1) + sourceSize: MTLSizeMake(NSUInteger(copySize.width()), NSUInteger(copySize.height()), 1) toTexture: dstD->d->tex - destinationSlice: NSUInteger(u.copy.desc.destinationLayer()) - destinationLevel: NSUInteger(u.copy.desc.destinationLevel()) + destinationSlice: NSUInteger(u.desc.destinationLayer()) + destinationLevel: NSUInteger(u.desc.destinationLevel()) destinationOrigin: MTLOriginMake(NSUInteger(dp.x()), NSUInteger(dp.y()), 0)]; srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - QRhiMetalData::ActiveReadback aRb; - aRb.activeFrameSlot = currentFrameSlot; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + QRhiMetalData::TextureReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.desc = u.rb; + readback.result = u.result; - QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture()); + QMetalTexture *texD = QRHI_RES(QMetalTexture, u.rb.texture()); QMetalSwapChain *swapChainD = nullptr; id<MTLTexture> src; QSize srcSize; @@ -1637,17 +1659,16 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate qWarning("Multisample texture cannot be read back"); continue; } - aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) - : texD->m_pixelSize; - aRb.format = texD->m_format; + readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); + readback.format = texD->m_format; src = texD->d->tex; - srcSize = texD->m_pixelSize; + srcSize = readback.pixelSize; texD->lastActiveFrameSlot = currentFrameSlot; } else { Q_ASSERT(currentSwapChain); swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain); - aRb.pixelSize = swapChainD->pixelSize; - aRb.format = swapChainD->d->rhiColorFormat; + readback.pixelSize = swapChainD->pixelSize; + readback.format = swapChainD->d->rhiColorFormat; // Multisample swapchains need nothing special since resolving // happens when ending a renderpass. const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]); @@ -1656,28 +1677,28 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate } quint32 bpl = 0; - textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize); - aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared]; + textureFormatInfo(readback.format, readback.pixelSize, &bpl, &readback.bufSize); + readback.buf = [d->dev newBufferWithLength: readback.bufSize options: MTLResourceStorageModeShared]; - QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(aRb.buf)), + QRHI_PROF_F(newReadbackBuffer(qint64(qintptr(readback.buf)), texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD), - aRb.bufSize)); + readback.bufSize)); ensureBlit(); [blitEnc copyFromTexture: src - sourceSlice: NSUInteger(u.read.rb.layer()) - sourceLevel: NSUInteger(u.read.rb.level()) + sourceSlice: NSUInteger(u.rb.layer()) + sourceLevel: NSUInteger(u.rb.level()) sourceOrigin: MTLOriginMake(0, 0, 0) sourceSize: MTLSizeMake(NSUInteger(srcSize.width()), NSUInteger(srcSize.height()), 1) - toBuffer: aRb.buf + toBuffer: readback.buf destinationOffset: 0 destinationBytesPerRow: bpl destinationBytesPerImage: 0 options: MTLBlitOptionNone]; - d->activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex); + d->activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.dst); ensureBlit(); [blitEnc generateMipmapsForTexture: utexD->d->tex]; utexD->lastActiveFrameSlot = currentFrameSlot; @@ -1697,14 +1718,13 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) { const int idx = bufD->d->slotted ? currentFrameSlot : 0; - QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]); - if (updates.isEmpty()) + if (bufD->d->pendingUpdates[idx].isEmpty()) return; void *p = [bufD->d->buf[idx] contents]; int changeBegin = -1; int changeEnd = -1; - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) { + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->d->pendingUpdates[idx])) { Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf)); memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); if (changeBegin == -1 || u.offset < changeBegin) @@ -1715,7 +1735,7 @@ void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD) if (changeBegin >= 0 && bufD->d->managed) [bufD->d->buf[idx] didModifyRange: NSMakeRange(NSUInteger(changeBegin), NSUInteger(changeEnd - changeBegin))]; - updates.clear(); + bufD->d->pendingUpdates[idx].clear(); } void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) @@ -1951,22 +1971,22 @@ void QRhiMetal::finishActiveReadbacks(bool forced) QVarLengthArray<std::function<void()>, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]); - if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) { - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); - void *p = [aRb.buf contents]; - memcpy(aRb.result->data.data(), p, aRb.bufSize); - [aRb.buf release]; + for (int i = d->activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiMetalData::TextureReadback &readback(d->activeTextureReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; + readback.result->data.resize(int(readback.bufSize)); + void *p = [readback.buf contents]; + memcpy(readback.result->data.data(), p, readback.bufSize); + [readback.buf release]; - QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(aRb.buf)))); + QRHI_PROF_F(releaseReadbackBuffer(qint64(qintptr(readback.buf)))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - d->activeReadbacks.removeAt(i); + d->activeTextureReadbacks.removeAt(i); } } @@ -2042,7 +2062,6 @@ bool QMetalBuffer::build() for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) { if (i == 0 || d->slotted) { d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts]; - d->pendingUpdates[i].reserve(16); if (!m_objectName.isEmpty()) { if (!d->slotted) { d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()]; @@ -3120,6 +3139,8 @@ bool QMetalGraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiMetal); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; // same binding space for vertex and constant buffers - work it around const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1; diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp index 487afd3ed1..fe606f971f 100644 --- a/src/gui/rhi/qrhinull.cpp +++ b/src/gui/rhi/qrhinull.cpp @@ -36,6 +36,7 @@ #include "qrhinull_p_p.h" #include <qmath.h> +#include <QPainter> QT_BEGIN_NAMESPACE @@ -385,27 +386,125 @@ QRhi::FrameOpResult QRhiNull::finish() return QRhi::FrameOpSuccess; } +void QRhiNull::simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *texD = QRHI_RES(QNullTexture, u.dst); + for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { + for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) { + if (!subresDesc.image().isNull()) { + const QImage src = subresDesc.image(); + QPainter painter(&texD->image[layer][level]); + const QSize srcSize = subresDesc.sourceSize().isEmpty() + ? src.size() : subresDesc.sourceSize(); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(subresDesc.destinationTopLeft(), src, + QRect(subresDesc.sourceTopLeft(), srcSize)); + } else if (!subresDesc.data().isEmpty()) { + const QSize subresSize = q->sizeForMipLevel(level, texD->pixelSize()); + int w = subresSize.width(); + int h = subresSize.height(); + if (!subresDesc.sourceSize().isEmpty()) { + w = subresDesc.sourceSize().width(); + h = subresDesc.sourceSize().height(); + } + // sourceTopLeft is not supported on this path as per QRhi docs + const char *src = subresDesc.data().constData(); + const int srcBpl = w * 4; + const QPoint dstOffset = subresDesc.destinationTopLeft(); + uchar *dst = texD->image[layer][level].bits(); + const int dstBpl = texD->image[layer][level].bytesPerLine(); + for (int y = 0; y < h; ++y) { + memcpy(dst + dstOffset.x() * 4 + (y + dstOffset.y()) * dstBpl, + src + y * srcBpl, + size_t(srcBpl)); + } + } + } + } + } +} + +void QRhiNull::simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *srcD = QRHI_RES(QNullTexture, u.src); + QNullTexture *dstD = QRHI_RES(QNullTexture, u.dst); + const QImage &srcImage(srcD->image[u.desc.sourceLayer()][u.desc.sourceLevel()]); + QImage &dstImage(dstD->image[u.desc.destinationLayer()][u.desc.destinationLevel()]); + const QPoint dstPos = u.desc.destinationTopLeft(); + const QSize size = u.desc.pixelSize().isEmpty() ? srcD->pixelSize() : u.desc.pixelSize(); + const QPoint srcPos = u.desc.sourceTopLeft(); + + QPainter painter(&dstImage); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(QRect(dstPos, size), srcImage, QRect(srcPos, size)); +} + +void QRhiNull::simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u) +{ + QNullTexture *texD = QRHI_RES(QNullTexture, u.dst); + const QSize baseSize = texD->pixelSize(); + const int levelCount = q->mipLevelsForSize(baseSize); + for (int level = 1; level < levelCount; ++level) + texD->image[0][level] = texD->image[0][0].scaled(q->sizeForMipLevel(level, baseSize)); +} + void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) { Q_UNUSED(cb); QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate + || u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) + { + QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); + memcpy(bufD->data.data() + u.offset, u.data.constData(), size_t(u.data.size())); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QRhiBufferReadbackResult *result = u.result; + result->data.resize(u.readSize); + QNullBuffer *bufD = QRHI_RES(QNullBuffer, u.buf); + memcpy(result->data.data(), bufD->data.constData() + u.offset, size_t(u.readSize)); + if (result->completed) + result->completed(); + } + } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { - if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - QRhiReadbackResult *result = u.read.result; - QRhiTexture *tex = u.read.rb.texture(); - if (tex) { - result->format = tex->format(); - result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize()); + if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { + if (u.dst->format() == QRhiTexture::RGBA8) + simulateTextureUpload(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { + if (u.src->format() == QRhiTexture::RGBA8 && u.dst->format() == QRhiTexture::RGBA8) + simulateTextureCopy(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { + QRhiReadbackResult *result = u.result; + QNullTexture *texD = QRHI_RES(QNullTexture, u.rb.texture()); + if (texD) { + result->format = texD->format(); + result->pixelSize = q->sizeForMipLevel(u.rb.level(), texD->pixelSize()); } else { Q_ASSERT(currentSwapChain); result->format = QRhiTexture::RGBA8; result->pixelSize = currentSwapChain->currentPixelSize(); } + quint32 bytesPerLine = 0; quint32 byteSize = 0; - textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize); - result->data.fill(0, byteSize); + textureFormatInfo(result->format, result->pixelSize, &bytesPerLine, &byteSize); + if (texD && texD->format() == QRhiTexture::RGBA8) { + result->data.resize(int(byteSize)); + const QImage &src(texD->image[u.rb.layer()][u.rb.level()]); + char *dst = result->data.data(); + for (int y = 0, h = src.height(); y < h; ++y) { + memcpy(dst, src.constScanLine(y), bytesPerLine); + dst += bytesPerLine; + } + } else { + result->data.fill(0, int(byteSize)); + } if (result->completed) result->completed(); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + if (u.dst->format() == QRhiTexture::RGBA8) + simulateTextureGenMips(u); } } ud->free(); @@ -454,14 +553,18 @@ QNullBuffer::~QNullBuffer() void QNullBuffer::release() { + data.clear(); + QRHI_PROF; QRHI_PROF_F(releaseBuffer(this)); } bool QNullBuffer::build() { + data.fill('\0', m_size); + QRHI_PROF; - QRHI_PROF_F(newBuffer(this, m_size, 1, 0)); + QRHI_PROF_F(newBuffer(this, uint(m_size), 1, 0)); return true; } @@ -513,22 +616,36 @@ void QNullTexture::release() bool QNullTexture::build() { + QRHI_RES_RHI(QRhiNull); const bool isCube = m_flags.testFlag(CubeMap); const bool hasMipMaps = m_flags.testFlag(MipMapped); QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; - const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1; + const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; + const int layerCount = isCube ? 6 : 1; + + if (m_format == RGBA8) { + for (int layer = 0; layer < layerCount; ++layer) { + for (int level = 0; level < mipLevelCount; ++level) { + image[layer][level] = QImage(rhiD->q->sizeForMipLevel(level, size), + QImage::Format_RGBA8888_Premultiplied); + image[layer][level].fill(Qt::yellow); + } + } + } + QRHI_PROF; - QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1)); + QRHI_PROF_F(newTexture(this, true, mipLevelCount, layerCount, 1)); return true; } bool QNullTexture::buildFrom(const QRhiNativeHandles *src) { Q_UNUSED(src); + QRHI_RES_RHI(QRhiNull); const bool isCube = m_flags.testFlag(CubeMap); const bool hasMipMaps = m_flags.testFlag(MipMapped); QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; - const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1; + const int mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1; QRHI_PROF; QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1)); return true; @@ -690,6 +807,10 @@ void QNullGraphicsPipeline::release() bool QNullGraphicsPipeline::build() { + QRHI_RES_RHI(QRhiNull); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + return true; } diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h index ee301d247b..ce517bfa63 100644 --- a/src/gui/rhi/qrhinull_p_p.h +++ b/src/gui/rhi/qrhinull_p_p.h @@ -59,6 +59,8 @@ struct QNullBuffer : public QRhiBuffer ~QNullBuffer(); void release() override; bool build() override; + + QByteArray data; }; struct QNullRenderBuffer : public QRhiRenderBuffer @@ -82,6 +84,7 @@ struct QNullTexture : public QRhiTexture const QRhiNativeHandles *nativeHandles() override; QRhiNullTextureNativeHandles nativeHandlesStruct; + QImage image[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS]; }; struct QNullSampler : public QRhiSampler @@ -286,6 +289,10 @@ public: void releaseCachedResources() override; bool isDeviceLost() const override; + void simulateTextureUpload(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureCopy(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + void simulateTextureGenMips(const QRhiResourceUpdateBatchPrivate::TextureOp &u); + QRhiNullNativeHandles nativeHandlesStruct; QRhiSwapChain *currentSwapChain = nullptr; QNullCommandBuffer offscreenCommandBuffer; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index d07777d63a..2d69abb36b 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -363,6 +363,11 @@ bool QRhiVulkan::create(QRhi::Flags flags) Q_UNUSED(flags); Q_ASSERT(inst); + if (!inst->isValid()) { + qWarning("Vulkan instance is not valid"); + return false; + } + globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application f = inst->functions(); @@ -2649,100 +2654,164 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates); QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) { - QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); - Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); - for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) - bufD->pendingDynamicUpdates[i].append(u); - } + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { + if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); + for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) + bufD->pendingDynamicUpdates[i].append(u); + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); + Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + + if (!bufD->stagingBuffers[currentFrameSlot]) { + VkBufferCreateInfo bufferInfo; + memset(&bufferInfo, 0, sizeof(bufferInfo)); + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + // must cover the entire buffer - this way multiple, partial updates per frame + // are supported even when the staging buffer is reused (Static) + bufferInfo.size = VkDeviceSize(bufD->m_size); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo allocInfo; + memset(&allocInfo, 0, sizeof(allocInfo)); + allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + + VmaAllocation allocation; + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, + &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr); + if (err == VK_SUCCESS) { + bufD->stagingAllocations[currentFrameSlot] = allocation; + QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); + } else { + qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err); + continue; + } + } - for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) { - QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); - Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); - Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); + void *p = nullptr; + VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]); + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err != VK_SUCCESS) { + qWarning("Failed to map buffer: %d", err); + continue; + } + memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); + vmaUnmapMemory(toVmaAllocator(allocator), a); + vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size())); - if (!bufD->stagingBuffers[currentFrameSlot]) { - VkBufferCreateInfo bufferInfo; - memset(&bufferInfo, 0, sizeof(bufferInfo)); - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - // must cover the entire buffer - this way multiple, partial updates per frame - // are supported even when the staging buffer is reused (Static) - bufferInfo.size = VkDeviceSize(bufD->m_size); - bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + trackedBufferBarrier(cbD, bufD, 0, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - VmaAllocationCreateInfo allocInfo; - memset(&allocInfo, 0, sizeof(allocInfo)); - allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; + VkBufferCopy copyInfo; + memset(©Info, 0, sizeof(copyInfo)); + copyInfo.srcOffset = VkDeviceSize(u.offset); + copyInfo.dstOffset = VkDeviceSize(u.offset); + copyInfo.size = VkDeviceSize(u.data.size()); - VmaAllocation allocation; - VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, - &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr); - if (err == VK_SUCCESS) { - bufD->stagingAllocations[currentFrameSlot] = allocation; - QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); - } else { - qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err); - continue; - } - } + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; + cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; + cmd.args.copyBuffer.dst = bufD->buffers[0]; + cmd.args.copyBuffer.desc = copyInfo; + cbD->commands.append(cmd); - void *p = nullptr; - VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]); - VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); - if (err != VK_SUCCESS) { - qWarning("Failed to map buffer: %d", err); - continue; - } - memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); - vmaUnmapMemory(toVmaAllocator(allocator), a); - vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(u.offset), VkDeviceSize(u.data.size())); + // Where's the barrier for read-after-write? (assuming the common case + // of binding this buffer as vertex/index, or, less likely, as uniform + // buffer, in a renderpass later on) That is handled by the pass + // resource tracking: the appropriate pipeline barrier will be + // generated and recorded right before the renderpass, that binds this + // buffer in one of its commands, gets its BeginRenderPass recorded. - trackedBufferBarrier(cbD, bufD, 0, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + bufD->lastActiveFrameSlot = currentFrameSlot; - VkBufferCopy copyInfo; - memset(©Info, 0, sizeof(copyInfo)); - copyInfo.srcOffset = VkDeviceSize(u.offset); - copyInfo.dstOffset = VkDeviceSize(u.offset); - copyInfo.size = VkDeviceSize(u.data.size()); + if (bufD->m_type == QRhiBuffer::Immutable) { + QRhiVulkan::DeferredReleaseEntry e; + e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; + e.lastActiveFrameSlot = currentFrameSlot; + e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; + e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; + bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; + bufD->stagingAllocations[currentFrameSlot] = nullptr; + releaseQueue.append(e); + QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); + } + } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { + QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); + if (bufD->m_type == QRhiBuffer::Dynamic) { + executeBufferHostWritesForCurrentFrame(bufD); + void *p = nullptr; + VmaAllocation a = toVmaAllocation(bufD->allocations[currentFrameSlot]); + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err == VK_SUCCESS) { + u.result->data.resize(u.readSize); + memcpy(u.result->data.data(), reinterpret_cast<char *>(p) + u.offset, size_t(u.readSize)); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } + if (u.result->completed) + u.result->completed(); + } else { + // Non-Dynamic buffers may not be host visible, so have to + // create a readback buffer, enqueue a copy from + // bufD->buffers[0] to this buffer, and then once the command + // buffer completes, copy the data out of the host visible + // readback buffer. Quite similar to what we do for texture + // readbacks. + BufferReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.result = u.result; + readback.byteSize = u.readSize; + + VkBufferCreateInfo bufferInfo; + memset(&bufferInfo, 0, sizeof(bufferInfo)); + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = VkDeviceSize(readback.byteSize); + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + VmaAllocationCreateInfo allocInfo; + memset(&allocInfo, 0, sizeof(allocInfo)); + allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; + + VmaAllocation allocation; + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); + if (err == VK_SUCCESS) { + readback.stagingAlloc = allocation; + QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize))); + } else { + qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); + continue; + } - QVkCommandBuffer::Command cmd; - cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; - cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; - cmd.args.copyBuffer.dst = bufD->buffers[0]; - cmd.args.copyBuffer.desc = copyInfo; - cbD->commands.append(cmd); + trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); - // Where's the barrier for read-after-write? (assuming the common case - // of binding this buffer as vertex/index, or, less likely, as uniform - // buffer, in a renderpass later on) That is handled by the pass - // resource tracking: the appropriate pipeline barrier will be - // generated and recorded right before the renderpass, that binds this - // buffer in one of its commands, gets its BeginRenderPass recorded. + VkBufferCopy copyInfo; + memset(©Info, 0, sizeof(copyInfo)); + copyInfo.srcOffset = VkDeviceSize(u.offset); + copyInfo.size = VkDeviceSize(u.readSize); - bufD->lastActiveFrameSlot = currentFrameSlot; + QVkCommandBuffer::Command cmd; + cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; + cmd.args.copyBuffer.src = bufD->buffers[0]; + cmd.args.copyBuffer.dst = readback.stagingBuf; + cmd.args.copyBuffer.desc = copyInfo; + cbD->commands.append(cmd); - if (bufD->m_type == QRhiBuffer::Immutable) { - QRhiVulkan::DeferredReleaseEntry e; - e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; - e.lastActiveFrameSlot = currentFrameSlot; - e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; - e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; - bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; - bufD->stagingAllocations[currentFrameSlot] = nullptr; - releaseQueue.append(e); - QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); + bufD->lastActiveFrameSlot = currentFrameSlot; + + activeBufferReadbacks.append(readback); + } } } for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { - QVkTexture *utexD = QRHI_RES(QVkTexture, u.upload.tex); + QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos VkDeviceSize stagingSize = 0; for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level])) + for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.subresDesc[layer][level])) stagingSize += subresUploadByteSize(subresDesc); } } @@ -2780,7 +2849,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { - const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.upload.subresDesc[layer][level]); + const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]); if (srd.isEmpty()) continue; for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) { @@ -2821,36 +2890,37 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat utexD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { - Q_ASSERT(u.copy.src && u.copy.dst); - if (u.copy.src == u.copy.dst) { + Q_ASSERT(u.src && u.dst); + if (u.src == u.dst) { qWarning("Texture copy with matching source and destination is not supported"); continue; } - QVkTexture *srcD = QRHI_RES(QVkTexture, u.copy.src); - QVkTexture *dstD = QRHI_RES(QVkTexture, u.copy.dst); + QVkTexture *srcD = QRHI_RES(QVkTexture, u.src); + QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst); VkImageCopy region; memset(®ion, 0, sizeof(region)); region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.srcSubresource.mipLevel = uint32_t(u.copy.desc.sourceLevel()); - region.srcSubresource.baseArrayLayer = uint32_t(u.copy.desc.sourceLayer()); + region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel()); + region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer()); region.srcSubresource.layerCount = 1; - region.srcOffset.x = u.copy.desc.sourceTopLeft().x(); - region.srcOffset.y = u.copy.desc.sourceTopLeft().y(); + region.srcOffset.x = u.desc.sourceTopLeft().x(); + region.srcOffset.y = u.desc.sourceTopLeft().y(); region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.dstSubresource.mipLevel = uint32_t(u.copy.desc.destinationLevel()); - region.dstSubresource.baseArrayLayer = uint32_t(u.copy.desc.destinationLayer()); + region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel()); + region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer()); region.dstSubresource.layerCount = 1; - region.dstOffset.x = u.copy.desc.destinationTopLeft().x(); - region.dstOffset.y = u.copy.desc.destinationTopLeft().y(); + region.dstOffset.x = u.desc.destinationTopLeft().x(); + region.dstOffset.y = u.desc.destinationTopLeft().y(); - const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize(); - region.extent.width = uint32_t(size.width()); - region.extent.height = uint32_t(size.height()); + const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize); + const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); + region.extent.width = uint32_t(copySize.width()); + region.extent.height = uint32_t(copySize.height()); region.extent.depth = 1; trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, @@ -2869,21 +2939,20 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { - ActiveReadback aRb; - aRb.activeFrameSlot = currentFrameSlot; - aRb.desc = u.read.rb; - aRb.result = u.read.result; + TextureReadback readback; + readback.activeFrameSlot = currentFrameSlot; + readback.desc = u.rb; + readback.result = u.result; - QVkTexture *texD = QRHI_RES(QVkTexture, u.read.rb.texture()); + QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture()); QVkSwapChain *swapChainD = nullptr; if (texD) { if (texD->samples > VK_SAMPLE_COUNT_1_BIT) { qWarning("Multisample texture cannot be read back"); continue; } - aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) - : texD->m_pixelSize; - aRb.format = texD->m_format; + readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize); + readback.format = texD->m_format; texD->lastActiveFrameSlot = currentFrameSlot; } else { Q_ASSERT(currentSwapChain); @@ -2892,21 +2961,21 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat qWarning("Swapchain does not support readback"); continue; } - aRb.pixelSize = swapChainD->pixelSize; - aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr); - if (aRb.format == QRhiTexture::UnknownFormat) + readback.pixelSize = swapChainD->pixelSize; + readback.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr); + if (readback.format == QRhiTexture::UnknownFormat) continue; // Multisample swapchains need nothing special since resolving // happens when ending a renderpass. } - textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize); + textureFormatInfo(readback.format, readback.pixelSize, nullptr, &readback.byteSize); - // Create a host visible buffer. + // Create a host visible readback buffer. VkBufferCreateInfo bufferInfo; memset(&bufferInfo, 0, sizeof(bufferInfo)); bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = aRb.bufSize; + bufferInfo.size = readback.byteSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; VmaAllocationCreateInfo allocInfo; @@ -2914,14 +2983,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; VmaAllocation allocation; - VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &aRb.buf, &allocation, nullptr); + VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &readback.stagingBuf, &allocation, nullptr); if (err == VK_SUCCESS) { - aRb.bufAlloc = allocation; - QRHI_PROF_F(newReadbackBuffer(qint64(aRb.buf), + readback.stagingAlloc = allocation; + QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD), - aRb.bufSize)); + readback.byteSize)); } else { - qWarning("Failed to create readback buffer of size %u: %d", aRb.bufSize, err); + qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err); continue; } @@ -2930,11 +2999,11 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat memset(©Desc, 0, sizeof(copyDesc)); copyDesc.bufferOffset = 0; copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyDesc.imageSubresource.mipLevel = uint32_t(u.read.rb.level()); - copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.read.rb.layer()); + copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level()); + copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer()); copyDesc.imageSubresource.layerCount = 1; - copyDesc.imageExtent.width = uint32_t(aRb.pixelSize.width()); - copyDesc.imageExtent.height = uint32_t(aRb.pixelSize.height()); + copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width()); + copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height()); copyDesc.imageExtent.depth = 1; if (texD) { @@ -2944,7 +3013,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; cmd.args.copyImageToBuffer.src = texD->image; cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout; - cmd.args.copyImageToBuffer.dst = aRb.buf; + cmd.args.copyImageToBuffer.dst = readback.stagingBuf; cmd.args.copyImageToBuffer.desc = copyDesc; cbD->commands.append(cmd); } else { @@ -2969,14 +3038,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; cmd.args.copyImageToBuffer.src = image; cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - cmd.args.copyImageToBuffer.dst = aRb.buf; + cmd.args.copyImageToBuffer.dst = readback.stagingBuf; cmd.args.copyImageToBuffer.desc = copyDesc; cbD->commands.append(cmd); } - activeReadbacks.append(aRb); - } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) { - QVkTexture *utexD = QRHI_RES(QVkTexture, u.mipgen.tex); + activeTextureReadbacks.append(readback); + } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { + QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips)); int w = utexD->m_pixelSize.width(); int h = utexD->m_pixelSize.height(); @@ -2993,14 +3062,14 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origAccess, VK_ACCESS_TRANSFER_READ_BIT, origStage, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level - 1, 1); } else { subresourceBarrier(cbD, utexD->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level - 1, 1); } @@ -3008,7 +3077,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origAccess, VK_ACCESS_TRANSFER_WRITE_BIT, origStage, VK_PIPELINE_STAGE_TRANSFER_BIT, - u.mipgen.layer, 1, + u.layer, 1, level, 1); VkImageBlit region; @@ -3016,7 +3085,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.srcSubresource.mipLevel = uint32_t(level) - 1; - region.srcSubresource.baseArrayLayer = uint32_t(u.mipgen.layer); + region.srcSubresource.baseArrayLayer = uint32_t(u.layer); region.srcSubresource.layerCount = 1; region.srcOffsets[1].x = qMax(1, w); @@ -3025,7 +3094,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.dstSubresource.mipLevel = uint32_t(level); - region.dstSubresource.baseArrayLayer = uint32_t(u.mipgen.layer); + region.dstSubresource.baseArrayLayer = uint32_t(u.layer); region.dstSubresource.layerCount = 1; region.dstOffsets[1].x = qMax(1, w >> 1); @@ -3051,13 +3120,13 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout, VK_ACCESS_TRANSFER_READ_BIT, origAccess, VK_PIPELINE_STAGE_TRANSFER_BIT, origStage, - u.mipgen.layer, 1, + u.layer, 1, 0, int(utexD->mipLevelCount) - 1); subresourceBarrier(cbD, utexD->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout, VK_ACCESS_TRANSFER_WRITE_BIT, origAccess, VK_PIPELINE_STAGE_TRANSFER_BIT, origStage, - u.mipgen.layer, 1, + u.layer, 1, int(utexD->mipLevelCount) - 1, 1); } @@ -3070,8 +3139,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) { - QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->pendingDynamicUpdates[currentFrameSlot]); - if (updates.isEmpty()) + if (bufD->pendingDynamicUpdates[currentFrameSlot].isEmpty()) return; Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); @@ -3087,7 +3155,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) } int changeBegin = -1; int changeEnd = -1; - for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) { + for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(bufD->pendingDynamicUpdates[currentFrameSlot])) { Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf)); memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), size_t(u.data.size())); if (changeBegin == -1 || u.offset < changeBegin) @@ -3099,7 +3167,7 @@ void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD) if (changeBegin >= 0) vmaFlushAllocation(toVmaAllocator(allocator), a, VkDeviceSize(changeBegin), VkDeviceSize(changeEnd - changeBegin)); - updates.clear(); + bufD->pendingDynamicUpdates[currentFrameSlot].clear(); } static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator) @@ -3193,29 +3261,53 @@ void QRhiVulkan::finishActiveReadbacks(bool forced) QVarLengthArray<std::function<void()>, 4> completedCallbacks; QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); - for (int i = activeReadbacks.count() - 1; i >= 0; --i) { - const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]); - if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) { - aRb.result->format = aRb.format; - aRb.result->pixelSize = aRb.pixelSize; - aRb.result->data.resize(int(aRb.bufSize)); + for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { + const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + readback.result->format = readback.format; + readback.result->pixelSize = readback.pixelSize; + VmaAllocation a = toVmaAllocation(readback.stagingAlloc); void *p = nullptr; - VmaAllocation a = toVmaAllocation(aRb.bufAlloc); VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); - if (err != VK_SUCCESS) { - qWarning("Failed to map readback buffer: %d", err); - continue; + if (err == VK_SUCCESS && p) { + readback.result->data.resize(int(readback.byteSize)); + memcpy(readback.result->data.data(), p, readback.byteSize); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } else { + qWarning("Failed to map texture readback buffer of size %u: %d", readback.byteSize, err); } - memcpy(aRb.result->data.data(), p, aRb.bufSize); - vmaUnmapMemory(toVmaAllocator(allocator), a); - vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a); - QRHI_PROF_F(releaseReadbackBuffer(qint64(aRb.buf))); + vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); + QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); - if (aRb.result->completed) - completedCallbacks.append(aRb.result->completed); + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); - activeReadbacks.removeAt(i); + activeTextureReadbacks.removeAt(i); + } + } + + for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { + const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]); + if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { + VmaAllocation a = toVmaAllocation(readback.stagingAlloc); + void *p = nullptr; + VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p); + if (err == VK_SUCCESS && p) { + readback.result->data.resize(readback.byteSize); + memcpy(readback.result->data.data(), p, size_t(readback.byteSize)); + vmaUnmapMemory(toVmaAllocator(allocator), a); + } else { + qWarning("Failed to map buffer readback buffer of size %d: %d", readback.byteSize, err); + } + + vmaDestroyBuffer(toVmaAllocator(allocator), readback.stagingBuf, a); + QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); + + if (readback.result->completed) + completedCallbacks.append(readback.result->completed); + + activeBufferReadbacks.removeAt(i); } } @@ -3733,6 +3825,10 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const return true; case QRhi::TriangleFanTopology: return true; + case QRhi::ReadBackNonUniformBuffer: + return true; + case QRhi::ReadBackNonBaseMipLevel: + return true; default: Q_UNREACHABLE(); return false; @@ -4902,7 +4998,7 @@ bool QVkBuffer::build() allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; } else { allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; } QRHI_RES_RHI(QRhiVulkan); @@ -4916,11 +5012,7 @@ bool QVkBuffer::build() err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr); if (err != VK_SUCCESS) break; - allocations[i] = allocation; - if (m_type == Dynamic) - pendingDynamicUpdates[i].reserve(16); - rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName, m_type == Dynamic ? i : -1); } @@ -5798,6 +5890,9 @@ bool QVkGraphicsPipeline::build() release(); QRHI_RES_RHI(QRhiVulkan); + if (!rhiD->sanityCheckGraphicsPipeline(this)) + return false; + if (!rhiD->ensurePipelineCache()) return false; diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h index 7bd20b3671..d0e1e6758b 100644 --- a/src/gui/rhi/qrhivulkan_p_p.h +++ b/src/gui/rhi/qrhivulkan_p_p.h @@ -79,7 +79,7 @@ struct QVkBuffer : public QRhiBuffer VkBuffer buffers[QVK_FRAMES_IN_FLIGHT]; QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT]; - QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; + QVarLengthArray<QRhiResourceUpdateBatchPrivate::BufferOp, 16> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT]; VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT]; QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT]; struct UsageState { @@ -853,17 +853,25 @@ public: VkFence cmdFence = VK_NULL_HANDLE; } ofr; - struct ActiveReadback { + struct TextureReadback { int activeFrameSlot = -1; QRhiReadbackDescription desc; QRhiReadbackResult *result; - VkBuffer buf; - QVkAlloc bufAlloc; - quint32 bufSize; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + quint32 byteSize; QSize pixelSize; QRhiTexture::Format format; }; - QVector<ActiveReadback> activeReadbacks; + QVector<TextureReadback> activeTextureReadbacks; + struct BufferReadback { + int activeFrameSlot = -1; + QRhiBufferReadbackResult *result; + int byteSize; + VkBuffer stagingBuf; + QVkAlloc stagingAlloc; + }; + QVector<BufferReadback> activeBufferReadbacks; struct DeferredReleaseEntry { enum Type { @@ -933,7 +941,8 @@ public: Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_MOVABLE_TYPE); -Q_DECLARE_TYPEINFO(QRhiVulkan::ActiveReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::TextureReadback, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QRhiVulkan::BufferReadback, Q_MOVABLE_TYPE); QT_END_NAMESPACE diff --git a/src/network/configure.json b/src/network/configure.json index a1cb77b6d1..f501465c91 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -102,13 +102,23 @@ "gssapi": { "label": "KRB5 GSSAPI Support", "test": { + "head": [ + "#if defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__))", + "# include <TargetConditionals.h>", + "# if defined(TARGET_OS_MAC) && TARGET_OS_MAC", + "# include <GSS/GSS.h>", + "# endif", + "#else", + "# include <gssapi/gssapi.h>", + "#endif" + ], "main": [ "gss_ctx_id_t ctx;", "gss_context_time(nullptr, ctx, nullptr);" ] }, - "headers": [ "gssapi/gssapi.h" ], "sources": [ + { "libs": "-framework GSS", "condition": "config.darwin" }, { "type": "pkgConfig", "args": "krb5-gssapi" }, "-lgssapi_krb5" ] diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 4100dfd784..33a30eb1cd 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -59,8 +59,12 @@ #define SECURITY_WIN32 1 #include <security.h> #elif QT_CONFIG(gssapi) // GSSAPI +#if defined(Q_OS_DARWIN) +#include <GSS/GSS.h> +#else #include <gssapi/gssapi.h> -#endif +#endif // Q_OS_DARWIN +#endif // Q_CONFIG(sspi) QT_BEGIN_NAMESPACE diff --git a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp index bf044229ff..c51db59e1f 100644 --- a/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp +++ b/src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp @@ -69,6 +69,7 @@ extern "C" { QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(qLcEvdevTouch, "qt.qpa.input") +Q_LOGGING_CATEGORY(qLcEvents, "qt.qpa.input.events") /* android (and perhaps some other linux-derived stuff) don't define everything * in linux/input.h, so we'll need to do that ourselves. @@ -539,6 +540,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) if (m_typeB) m_contacts[m_currentSlot].maj = m_currentData.maj; } else if (data->code == ABS_PRESSURE || data->code == ABS_MT_PRESSURE) { + if (Q_UNLIKELY(qLcEvents().isDebugEnabled())) + qCDebug(qLcEvents, "EV_ABS code 0x%x: pressure %d; bounding to [%d,%d]", + data->code, data->value, hw_pressure_min, hw_pressure_max); m_currentData.pressure = qBound(hw_pressure_min, data->value, hw_pressure_max); if (m_typeB || m_singleTouch) m_contacts[m_currentSlot].pressure = m_currentData.pressure; @@ -577,6 +581,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) m_lastTouchPoints = m_touchPoints; m_touchPoints.clear(); Qt::TouchPointStates combinedStates; + bool hasPressure = false; for (auto i = m_contacts.begin(), end = m_contacts.end(); i != end; /*erasing*/) { auto it = i++; @@ -607,6 +612,9 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) continue; } + if (contact.pressure) + hasPressure = true; + addTouchPoint(contact, &combinedStates); } @@ -651,7 +659,7 @@ void QEvdevTouchScreenData::processInputEvent(input_event *data) m_contacts.clear(); - if (!m_touchPoints.isEmpty() && combinedStates != Qt::TouchPointStationary) + if (!m_touchPoints.isEmpty() && (hasPressure || combinedStates != Qt::TouchPointStationary)) reportPoints(); } @@ -777,6 +785,9 @@ void QEvdevTouchScreenData::reportPoints() tp.pressure = tp.state == Qt::TouchPointReleased ? 0 : 1; else tp.pressure = (tp.pressure - hw_pressure_min) / qreal(hw_pressure_max - hw_pressure_min); + + if (Q_UNLIKELY(qLcEvents().isDebugEnabled())) + qCDebug(qLcEvents) << "reporting" << tp; } // Let qguiapp pick the target window. diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 6889c2e9e5..b70a03b311 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -9218,9 +9218,11 @@ void QWidget::mouseReleaseEvent(QMouseEvent *event) The default implementation calls mousePressEvent(). \note The widget will also receive mouse press and mouse release - events in addition to the double click event. It is up to the - developer to ensure that the application interprets these events - correctly. + events in addition to the double click event. And if another widget + that overlaps this widget disappears in response to press or + release events, then this widget will only receive the double click + event. It is up to the developer to ensure that the application + interprets these events correctly. \sa mousePressEvent(), mouseReleaseEvent(), mouseMoveEvent(), event(), QMouseEvent diff --git a/tests/auto/corelib/text/qstring/tst_qstring.cpp b/tests/auto/corelib/text/qstring/tst_qstring.cpp index cce3e601cd..c96210f53d 100644 --- a/tests/auto/corelib/text/qstring/tst_qstring.cpp +++ b/tests/auto/corelib/text/qstring/tst_qstring.cpp @@ -4593,6 +4593,8 @@ void tst_QString::fromLatin1() } #if QT_DEPRECATED_SINCE(5, 0) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED void tst_QString::fromAscii() { QString a; @@ -4613,6 +4615,7 @@ void tst_QString::fromAscii() a = QString::fromAscii("\0abcd", 5); QVERIFY(a.size() == 5); } +QT_WARNING_POP #endif void tst_QString::fromUcs4() diff --git a/tests/auto/gui/rhi/qrhi/data/compile.bat b/tests/auto/gui/rhi/qrhi/data/compile.bat new file mode 100644 index 0000000000..5b8a77b833 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/compile.bat @@ -0,0 +1,48 @@ +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Copyright (C) 2019 The Qt Company Ltd. +:: Contact: https://www.qt.io/licensing/ +:: +:: This file is part of the QtQuick module of the Qt Toolkit. +:: +:: $QT_BEGIN_LICENSE:LGPL$ +:: Commercial License Usage +:: Licensees holding valid commercial Qt licenses may use this file in +:: accordance with the commercial license agreement provided with the +:: Software or, alternatively, in accordance with the terms contained in +:: a written agreement between you and The Qt Company. For licensing terms +:: and conditions see https://www.qt.io/terms-conditions. For further +:: information use the contact form at https://www.qt.io/contact-us. +:: +:: GNU Lesser General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU Lesser +:: General Public License version 3 as published by the Free Software +:: Foundation and appearing in the file LICENSE.LGPL3 included in the +:: packaging of this file. Please review the following information to +:: ensure the GNU Lesser General Public License version 3 requirements +:: will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +:: +:: GNU General Public License Usage +:: Alternatively, this file may be used under the terms of the GNU +:: General Public License version 2.0 or (at your option) the GNU General +:: Public license version 3 or any later version approved by the KDE Free +:: Qt Foundation. The licenses are as published by the Free Software +:: Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +:: included in the packaging of this file. Please review the following +:: information to ensure the GNU General Public License requirements will +:: be met: https://www.gnu.org/licenses/gpl-2.0.html and +:: https://www.gnu.org/licenses/gpl-3.0.html. +:: +:: $QT_END_LICENSE$ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: Note the -c argument: we do not want runtime HLSL compilation since that is +:: not an option on UWP (WinRT). This means that running qsb must happen on Windows. + +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.vert.qsb simple.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simple.frag.qsb simple.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.vert.qsb simpletextured.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o simpletextured.frag.qsb simpletextured.frag +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.vert.qsb textured.vert +qsb --glsl "150,120,100 es" --hlsl 50 -c --msl 12 -o textured.frag.qsb textured.frag diff --git a/tests/auto/gui/rhi/qrhi/data/qt256.png b/tests/auto/gui/rhi/qrhi/data/qt256.png Binary files differnew file mode 100644 index 0000000000..30c621c9c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/qt256.png diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag b/tests/auto/gui/rhi/qrhi/data/simple.frag new file mode 100644 index 0000000000..2aa500e09a --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.frag @@ -0,0 +1,8 @@ +#version 440 + +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb Binary files differnew file mode 100644 index 0000000000..264b71ec0f --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert b/tests/auto/gui/rhi/qrhi/data/simple.vert new file mode 100644 index 0000000000..16ee61beca --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.vert @@ -0,0 +1,10 @@ +#version 440 + +layout(location = 0) in vec4 position; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb Binary files differnew file mode 100644 index 0000000000..59080b60c6 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag new file mode 100644 index 0000000000..630df7b807 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag @@ -0,0 +1,13 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(binding = 0) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb Binary files differnew file mode 100644 index 0000000000..f302702aa9 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert new file mode 100644 index 0000000000..1dd204f84d --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert @@ -0,0 +1,14 @@ +#version 440 + +layout(location = 0) in vec4 position; +layout(location = 1) in vec2 texcoord; + +layout(location = 0) out vec2 uv; + +out gl_PerVertex { vec4 gl_Position; }; + +void main() +{ + uv = texcoord; + gl_Position = position; +} diff --git a/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb Binary files differnew file mode 100644 index 0000000000..e4f12bfb9e --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/texture.frag b/tests/auto/gui/rhi/qrhi/data/texture.frag deleted file mode 100644 index e6021fe905..0000000000 --- a/tests/auto/gui/rhi/qrhi/data/texture.frag +++ /dev/null @@ -1,12 +0,0 @@ -#version 440 - -layout(location = 0) in vec2 v_texcoord; - -layout(location = 0) out vec4 fragColor; - -layout(binding = 1) uniform sampler2D tex; - -void main() -{ - fragColor = texture(tex, v_texcoord); -} diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag b/tests/auto/gui/rhi/qrhi/data/textured.frag new file mode 100644 index 0000000000..605410b028 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.frag @@ -0,0 +1,19 @@ +#version 440 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 matrix; + float opacity; +} ubuf; + +layout(binding = 1) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, uv); + c.a *= ubuf.opacity; + c.rgb *= c.a; + fragColor = c; +} diff --git a/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb Binary files differnew file mode 100644 index 0000000000..0a039137ec --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb diff --git a/tests/auto/gui/rhi/qrhi/data/texture.vert b/tests/auto/gui/rhi/qrhi/data/textured.vert index de486cb772..f1ccf2ee50 100644 --- a/tests/auto/gui/rhi/qrhi/data/texture.vert +++ b/tests/auto/gui/rhi/qrhi/data/textured.vert @@ -3,16 +3,17 @@ layout(location = 0) in vec4 position; layout(location = 1) in vec2 texcoord; -layout(location = 0) out vec2 v_texcoord; +layout(location = 0) out vec2 uv; layout(std140, binding = 0) uniform buf { - mat4 mvp; + mat4 matrix; + float opacity; } ubuf; out gl_PerVertex { vec4 gl_Position; }; void main() { - v_texcoord = texcoord; - gl_Position = ubuf.mvp * position; + uv = texcoord; + gl_Position = ubuf.matrix * position; } diff --git a/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb Binary files differnew file mode 100644 index 0000000000..7853f77943 --- /dev/null +++ b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp index 897613d525..768b227ecd 100644 --- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp +++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp @@ -30,10 +30,13 @@ #include <QThread> #include <QFile> #include <QOffscreenSurface> +#include <QPainter> + #include <QtGui/private/qrhi_p.h> #include <QtGui/private/qrhinull_p.h> #if QT_CONFIG(opengl) +# include <QOpenGLContext> # include <QtGui/private/qrhigles2_p.h> # define TST_GL #endif @@ -65,8 +68,29 @@ private slots: void initTestCase(); void cleanupTestCase(); + void rhiTestData(); void create_data(); void create(); + void nativeHandles_data(); + void nativeHandles(); + void resourceUpdateBatchBuffer_data(); + void resourceUpdateBatchBuffer(); + void resourceUpdateBatchRGBATextureUpload_data(); + void resourceUpdateBatchRGBATextureUpload(); + void resourceUpdateBatchRGBATextureCopy_data(); + void resourceUpdateBatchRGBATextureCopy(); + void resourceUpdateBatchRGBATextureMip_data(); + void resourceUpdateBatchRGBATextureMip(); + void invalidPipeline_data(); + void invalidPipeline(); + void renderToTextureSimple_data(); + void renderToTextureSimple(); + void renderToTextureTexturedQuad_data(); + void renderToTextureTexturedQuad(); + void renderToTextureTexturedQuadAndUniformBuffer_data(); + void renderToTextureTexturedQuadAndUniformBuffer(); + void renderToWindowSimple_data(); + void renderToWindowSimple(); private: struct { @@ -99,9 +123,26 @@ void tst_QRhi::initTestCase() #endif #ifdef TST_VK +#ifndef Q_OS_ANDROID + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_LUNARG_standard_validation") }); +#else + vulkanInstance.setLayers({ QByteArrayLiteral("VK_LAYER_GOOGLE_threading"), + QByteArrayLiteral("VK_LAYER_LUNARG_parameter_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_object_tracker"), + QByteArrayLiteral("VK_LAYER_LUNARG_core_validation"), + QByteArrayLiteral("VK_LAYER_LUNARG_image"), + QByteArrayLiteral("VK_LAYER_LUNARG_swapchain"), + QByteArrayLiteral("VK_LAYER_GOOGLE_unique_objects") }); +#endif + vulkanInstance.setExtensions(QByteArrayList() + << "VK_KHR_get_physical_device_properties2"); vulkanInstance.create(); initParams.vk.inst = &vulkanInstance; #endif + +#ifdef TST_D3D11 + initParams.d3d.enableDebugLayer = true; +#endif } void tst_QRhi::cleanupTestCase() @@ -113,7 +154,7 @@ void tst_QRhi::cleanupTestCase() delete fallbackSurface; } -void tst_QRhi::create_data() +void tst_QRhi::rhiTestData() { QTest::addColumn<QRhi::Implementation>("impl"); QTest::addColumn<QRhiInitParams *>("initParams"); @@ -134,6 +175,11 @@ void tst_QRhi::create_data() #endif } +void tst_QRhi::create_data() +{ + rhiTestData(); +} + static int aligned(int v, int a) { return (v + a - 1) & ~(a - 1); @@ -154,6 +200,8 @@ void tst_QRhi::create() QCOMPARE(rhi->backend(), impl); QCOMPARE(rhi->thread(), QThread::currentThread()); + // do a basic smoke test for the apis that do not directly render anything + int cleanupOk = 0; QRhi *rhiPtr = rhi.data(); auto cleanupFunc = [rhiPtr, &cleanupOk](QRhi *dyingRhi) { @@ -211,9 +259,11 @@ void tst_QRhi::create() const int texMin = rhi->resourceLimit(QRhi::TextureSizeMin); const int texMax = rhi->resourceLimit(QRhi::TextureSizeMax); const int maxAtt = rhi->resourceLimit(QRhi::MaxColorAttachments); + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); QVERIFY(texMin >= 1); QVERIFY(texMax >= texMin); QVERIFY(maxAtt >= 1); + QVERIFY(framesInFlight >= 1); QVERIFY(rhi->nativeHandles()); QVERIFY(rhi->profiler()); @@ -230,17 +280,1450 @@ void tst_QRhi::create() QRhi::NonFourAlignedEffectiveIndexBufferOffset, QRhi::NPOTTextureRepeat, QRhi::RedOrAlpha8IsRed, - QRhi::ElementIndexUint + QRhi::ElementIndexUint, + QRhi::Compute, + QRhi::WideLines, + QRhi::VertexShaderPointSize, + QRhi::BaseVertex, + QRhi::BaseInstance, + QRhi::TriangleFanTopology, + QRhi::ReadBackNonUniformBuffer, + QRhi::ReadBackNonBaseMipLevel }; for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i) rhi->isFeatureSupported(features[i]); QVERIFY(rhi->isTextureFormatSupported(QRhiTexture::RGBA8)); + rhi->releaseCachedResources(); + + QVERIFY(!rhi->isDeviceLost()); + rhi.reset(); QCOMPARE(cleanupOk, 1); } } +void tst_QRhi::nativeHandles_data() +{ + rhiTestData(); +} + +void tst_QRhi::nativeHandles() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing native handles"); + + // QRhi::nativeHandles() + { + const QRhiNativeHandles *rhiHandles = rhi->nativeHandles(); + Q_ASSERT(rhiHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanNativeHandles *vkHandles = static_cast<const QRhiVulkanNativeHandles *>(rhiHandles); + QVERIFY(vkHandles->physDev); + QVERIFY(vkHandles->dev); + QVERIFY(vkHandles->gfxQueueFamilyIdx >= 0); + QVERIFY(vkHandles->gfxQueue); + QVERIFY(vkHandles->cmdPool); + QVERIFY(vkHandles->vmemAllocator); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2NativeHandles *glHandles = static_cast<const QRhiGles2NativeHandles *>(rhiHandles); + QVERIFY(glHandles->context); + QVERIFY(glHandles->context->isValid()); + glHandles->context->doneCurrent(); + QVERIFY(!QOpenGLContext::currentContext()); + rhi->makeThreadLocalNativeContextCurrent(); + QVERIFY(QOpenGLContext::currentContext() == glHandles->context); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11NativeHandles *d3dHandles = static_cast<const QRhiD3D11NativeHandles *>(rhiHandles); + QVERIFY(d3dHandles->dev); + QVERIFY(d3dHandles->context); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalNativeHandles *mtlHandles = static_cast<const QRhiMetalNativeHandles *>(rhiHandles); + QVERIFY(mtlHandles->dev); + QVERIFY(mtlHandles->cmdQueue); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiTexture::nativeHandles() + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 256))); + QVERIFY(tex->build()); + + const QRhiNativeHandles *texHandles = tex->nativeHandles(); + QVERIFY(texHandles); + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanTextureNativeHandles *vkHandles = static_cast<const QRhiVulkanTextureNativeHandles *>(texHandles); + QVERIFY(vkHandles->image); + QVERIFY(vkHandles->layout >= 1); // VK_IMAGE_LAYOUT_GENERAL + QVERIFY(vkHandles->layout <= 8); // VK_IMAGE_LAYOUT_PREINITIALIZED + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + { + const QRhiGles2TextureNativeHandles *glHandles = static_cast<const QRhiGles2TextureNativeHandles *>(texHandles); + QVERIFY(glHandles->texture); + } + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + { + const QRhiD3D11TextureNativeHandles *d3dHandles = static_cast<const QRhiD3D11TextureNativeHandles *>(texHandles); + QVERIFY(d3dHandles->texture); + } + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalTextureNativeHandles *mtlHandles = static_cast<const QRhiMetalTextureNativeHandles *>(texHandles); + QVERIFY(mtlHandles->texture); + } + break; +#endif + default: + Q_ASSERT(false); + } + } + + // QRhiCommandBuffer::nativeHandles() + { + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + QVERIFY(result == QRhi::FrameOpSuccess); + QVERIFY(cb); + + const QRhiNativeHandles *cbHandles = cb->nativeHandles(); + // no null check here, backends where not applicable will return null + + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanCommandBufferNativeHandles *vkHandles = static_cast<const QRhiVulkanCommandBufferNativeHandles *>(cbHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->commandBuffer); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + { + const QRhiMetalCommandBufferNativeHandles *mtlHandles = static_cast<const QRhiMetalCommandBufferNativeHandles *>(cbHandles); + QVERIFY(mtlHandles); + QVERIFY(mtlHandles->commandBuffer); + QVERIFY(!mtlHandles->encoder); + + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + cb->beginPass(rt.data(), Qt::red, { 1.0f, 0 }); + QVERIFY(static_cast<const QRhiMetalCommandBufferNativeHandles *>(cb->nativeHandles())->encoder); + cb->endPass(); + } + break; +#endif + default: + Q_ASSERT(false); + } + + rhi->endOffscreenFrame(); + } + + // QRhiRenderPassDescriptor::nativeHandles() + { + QScopedPointer<QRhiTexture> tex(rhi->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 1, QRhiTexture::RenderTarget)); + QVERIFY(tex->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ tex.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + QVERIFY(rpDesc); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + const QRhiNativeHandles *rpHandles = rpDesc->nativeHandles(); + switch (impl) { + case QRhi::Null: + break; +#ifdef TST_VK + case QRhi::Vulkan: + { + const QRhiVulkanRenderPassNativeHandles *vkHandles = static_cast<const QRhiVulkanRenderPassNativeHandles *>(rpHandles); + QVERIFY(vkHandles); + QVERIFY(vkHandles->renderPass); + } + break; +#endif +#ifdef TST_GL + case QRhi::OpenGLES2: + break; +#endif +#ifdef TST_D3D11 + case QRhi::D3D11: + break; +#endif +#ifdef TST_MTL + case QRhi::Metal: + break; +#endif + default: + Q_ASSERT(false); + } + } +} + +static bool submitResourceUpdates(QRhi *rhi, QRhiResourceUpdateBatch *batch) +{ + QRhiCommandBuffer *cb = nullptr; + QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb); + if (result != QRhi::FrameOpSuccess) { + qWarning("beginOffscreenFrame returned %d", result); + return false; + } + if (!cb) { + qWarning("No command buffer from beginOffscreenFrame"); + return false; + } + cb->resourceUpdate(batch); + rhi->endOffscreenFrame(); + return true; +} + +void tst_QRhi::resourceUpdateBatchBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchBuffer() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing buffer resource updates"); + + const int bufferSize = 23; + const QByteArray a(bufferSize, 'A'); + const QByteArray b(bufferSize, 'B'); + + // dynamic buffer, updates, readback + { + QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->updateDynamicBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->updateDynamicBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + // Offscreen frames are synchronous, so the readback must have + // completed at this point. With swapchain frames this would not be the + // case. + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } + + // static buffer, updates, readback + { + QScopedPointer<QRhiBuffer> dynamicBuffer(rhi->newBuffer(QRhiBuffer::Static, QRhiBuffer::VertexBuffer, bufferSize)); + QVERIFY(dynamicBuffer->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + QVERIFY(batch); + + batch->uploadStaticBuffer(dynamicBuffer.data(), 10, bufferSize - 10, a.constData()); + batch->uploadStaticBuffer(dynamicBuffer.data(), 0, 12, b.constData()); + + QRhiBufferReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) + batch->readBackBuffer(dynamicBuffer.data(), 5, 10, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + if (rhi->isFeatureSupported(QRhi::ReadBackNonUniformBuffer)) { + QVERIFY(readCompleted); + QVERIFY(readResult.data.size() == 10); + QCOMPARE(readResult.data.left(7), QByteArrayLiteral("BBBBBBB")); + QCOMPARE(readResult.data.mid(7), QByteArrayLiteral("AAA")); + } else { + qDebug("Skipping verifying buffer contents because readback is not supported"); + } + } +} + +inline bool imageRGBAEquals(const QImage &a, const QImage &b) +{ + const int maxFuzz = 1; + + if (a.size() != b.size()) + return false; + + const QImage image0 = a.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + const QImage image1 = b.convertToFormat(QImage::Format_RGBA8888_Premultiplied); + + const int width = image0.width(); + const int height = image0.height(); + for (int y = 0; y < height; ++y) { + const quint32 *p0 = reinterpret_cast<const quint32 *>(image0.constScanLine(y)); + const quint32 *p1 = reinterpret_cast<const quint32 *>(image1.constScanLine(y)); + int x = width - 1; + while (x-- >= 0) { + const QRgb c0(*p0++); + const QRgb c1(*p1++); + const int red = qAbs(qRed(c0) - qRed(c1)); + const int green = qAbs(qGreen(c0) - qGreen(c1)); + const int blue = qAbs(qBlue(c0) - qBlue(c1)); + const int alpha = qAbs(qAlpha(c0) - qAlpha(c1)); + if (red > maxFuzz || green > maxFuzz || blue > maxFuzz || alpha > maxFuzz) + return false; + } + } + + return true; +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureUpload() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + QImage image(234, 123, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::red); + QPainter painter; + const QPoint greenRectPos(35, 50); + const QSize greenRectSize(100, 50); + painter.begin(&image); + painter.fillRect(QRect(greenRectPos, greenRectSize), Qt::green); + painter.end(); + + // simple image upload; uploading and reading back RGBA8 is supported by the Null backend even + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), image); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + // like with buffers, the readback is now complete due to endOffscreenFrame() + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // the same with raw data + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, image.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QRhiTextureUploadEntry upload(0, 0, { image.constBits(), int(image.sizeInBytes()) }); + QRhiTextureUploadDescription uploadDesc(upload); + batch->uploadTexture(texture.data(), uploadDesc); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, image.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(imageRGBAEquals(image, wrapperImage)); + } + + // partial image upload at a non-zero destination position + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // copy green pixels of copySize to (gap, gap), leaving a black bar of + // gap pixels on the left and top + QRhiTextureSubresourceUploadDescription desc; + desc.setImage(image); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + desc.setSourceTopLeft(greenRectPos); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } + + // the same (partial upload) with raw data as source + { + const QSize copySize(30, 40); + const int gap = 10; + const QSize fullSize(copySize.width() + gap, copySize.height() + gap); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, fullSize, + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QImage clearImage(fullSize, image.format()); + clearImage.fill(Qt::black); + batch->uploadTexture(texture.data(), clearImage); + + // SourceTopLeft is not supported for non-QImage-based uploads. + const QImage im = image.copy(QRect(greenRectPos, copySize)); + QRhiTextureSubresourceUploadDescription desc; + desc.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(im.constBits()), + int(im.sizeInBytes()))); + desc.setSourceSize(copySize); + desc.setDestinationTopLeft(QPoint(gap, gap)); + + batch->uploadTexture(texture.data(), QRhiTextureUploadDescription({ 0, 0, desc })); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QCOMPARE(readResult.format, QRhiTexture::RGBA8); + QCOMPARE(readResult.pixelSize, clearImage.size()); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + image.format()); + + QVERIFY(!imageRGBAEquals(clearImage, wrapperImage)); + + QImage expectedImage = clearImage; + QPainter painter(&expectedImage); + painter.fillRect(QRect(QPoint(gap, gap), QSize(copySize)), Qt::green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } + + // now a QImage from an actual file + { + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + inputImage = std::move(inputImage).convertToFormat(image.format()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), inputImage); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + inputImage.format()); + + QVERIFY(imageRGBAEquals(inputImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureCopy() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + QImage red(256, 256, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + QImage green(35, 73, red.format()); + green.fill(Qt::green); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + + QScopedPointer<QRhiTexture> redTexture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(redTexture->build()); + batch->uploadTexture(redTexture.data(), red); + + QScopedPointer<QRhiTexture> greenTexture(rhi->newTexture(QRhiTexture::RGBA8, green.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(greenTexture->build()); + batch->uploadTexture(greenTexture.data(), green); + + // 1. simple copy red -> texture; 2. subimage copy green -> texture; 3. partial subimage copy green -> texture + { + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), + 1, QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + // 1. + batch->copyTexture(texture.data(), redTexture.data()); + + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + QVERIFY(imageRGBAEquals(red, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 2. + QRhiTextureCopyDescription copyDesc; + copyDesc.setDestinationTopLeft(QPoint(15, 23)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + QImage expectedImage = red; + QPainter painter(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + + batch = rhi->nextResourceUpdateBatch(); + readCompleted = false; + + // 3. + copyDesc.setDestinationTopLeft(QPoint(125, 89)); + copyDesc.setSourceTopLeft(QPoint(5, 5)); + copyDesc.setPixelSize(QSize(26, 45)); + batch->copyTexture(texture.data(), greenTexture.data(), copyDesc); + + batch->readBackTexture(texture.data(), &readResult); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + wrapperImage = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + + painter.begin(&expectedImage); + painter.drawImage(copyDesc.destinationTopLeft(), green, + QRect(copyDesc.sourceTopLeft(), copyDesc.pixelSize())); + painter.end(); + + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip_data() +{ + rhiTestData(); +} + +void tst_QRhi::resourceUpdateBatchRGBATextureMip() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing texture resource updates"); + + + QImage red(512, 512, QImage::Format_RGBA8888_Premultiplied); + red.fill(Qt::red); + + const QRhiTexture::Flags textureFlags = + QRhiTexture::UsedAsTransferSource + | QRhiTexture::MipMapped + | QRhiTexture::UsedWithGenerateMips; + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, red.size(), 1, textureFlags)); + QVERIFY(texture->build()); + + QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch(); + batch->uploadTexture(texture.data(), red); + batch->generateMips(texture.data()); + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + + const int levelCount = rhi->mipLevelsForSize(red.size()); + QCOMPARE(levelCount, 10); + for (int level = 0; level < levelCount; ++level) { + batch = rhi->nextResourceUpdateBatch(); + + QRhiReadbackDescription readDesc(texture.data()); + readDesc.setLevel(level); + QRhiReadbackResult readResult; + bool readCompleted = false; + readResult.completed = [&readCompleted] { readCompleted = true; }; + batch->readBackTexture(readDesc, &readResult); + + QVERIFY(submitResourceUpdates(rhi.data(), batch)); + QVERIFY(readCompleted); + + const QSize expectedSize = rhi->sizeForMipLevel(level, texture->pixelSize()); + QCOMPARE(readResult.pixelSize, expectedSize); + + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + red.format()); + QImage expectedImage; + if (level == 0 || rhi->isFeatureSupported(QRhi::ReadBackNonBaseMipLevel)) { + // Compare to a scaled version; we can do this safely only because we + // only have plain red pixels in the source image. + expectedImage = red.scaled(expectedSize); + } else { + qDebug("Expecting all-zero image for level %d because reading back a level other than 0 is not supported", level); + expectedImage = QImage(readResult.pixelSize, red.format()); + expectedImage.fill(0); + } + QVERIFY(imageRGBAEquals(expectedImage, wrapperImage)); + } +} + +static QShader loadShader(const char *name) +{ + QFile f(QString::fromUtf8(name)); + if (f.open(QIODevice::ReadOnly)) { + const QByteArray contents = f.readAll(); + return QShader::fromSerialized(contents); + } + return QShader(); +} + +void tst_QRhi::invalidPipeline_data() +{ + rhiTestData(); +} + +void tst_QRhi::invalidPipeline() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing empty shader"); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, QSize(256, 256), 1, QRhiTexture::RenderTarget)); + QVERIFY(texture->build()); + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + + // no stages + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + QShader vs; + QShader fs; + + // no shaders in the stages + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + + // no vertex stage + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // no vertex inputs + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no renderpass descriptor + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(!pipeline->build()); + + // no shader resource bindings + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(!pipeline->build()); + + // correct + pipeline.reset(rhi->newGraphicsPipeline()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setRenderPassDescriptor(rpDesc.data()); + pipeline->setShaderResourceBindings(srb.data()); + QVERIFY(pipeline->build()); +} + +void tst_QRhi::renderToTextureSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureSimple() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + const QSize outputSize(1920, 1080); + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, outputSize, 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::blue, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport({ 0, 0, float(outputSize.width()), float(outputSize.height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); // non-owning, no copy needed because readResult outlives result + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + // Offscreen frames are synchronous, so the readback is guaranteed to + // complete at this point. This would not be the case with swapchain-based + // frames. + QCOMPARE(result.size(), texture->pixelSize()); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 100; + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, texture->pixelSize().width()); + + // The triangle is "pointing up" in the resulting image with OpenGL + // (because Y is up both in normalized device coordinates and in images) + // and Vulkan (because Y is down in both and the vertex data was specified + // with Y up in mind), but "pointing down" with D3D (because Y is up in NDC + // but down in images). + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + QVERIFY(redCount < blueCount); + else + QVERIFY(redCount > blueCount); +} + +void tst_QRhi::renderToTextureTexturedQuad_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuad() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + srb->setBindings({ + QRhiShaderResourceBinding::sampledTexture(0, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/simpletextured.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simpletextured.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult; + QImage result; + readResult.completed = [&readResult, &result] { + result = QImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result.isNull()); + + if (impl == QRhi::Null) + return; + + // Flip with D3D and Metal because these have Y down in images. Vulkan does + // not need this because there Y is down both in images and in NDC, which + // just happens to give correct results with our OpenGL-targeted vertex and + // UV data. + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) + result = std::move(result).mirrored(); + + // check a few points that are expected to match regardless of the implementation + QRgb white = qRgba(255, 255, 255, 255); + QCOMPARE(result.pixel(79, 77), white); + QCOMPARE(result.pixel(124, 81), white); + QCOMPARE(result.pixel(128, 149), white); + QCOMPARE(result.pixel(120, 189), white); + QCOMPARE(result.pixel(116, 185), white); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result.pixel(11, 45), empty); + QCOMPARE(result.pixel(246, 202), empty); + QCOMPARE(result.pixel(130, 18), empty); + QCOMPARE(result.pixel(4, 227), empty); + + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qRed(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(32, 52)) > 2 * qBlue(result.pixel(32, 52))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qRed(result.pixel(214, 191))); + QVERIFY(qGreen(result.pixel(214, 191)) > 2 * qBlue(result.pixel(214, 191))); +} + +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToTextureTexturedQuadAndUniformBuffer() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QImage inputImage; + inputImage.load(QLatin1String(":/data/qt256.png")); + QVERIFY(!inputImage.isNull()); + + QScopedPointer<QRhiTexture> texture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size(), 1, + QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); + QVERIFY(texture->build()); + + QScopedPointer<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget({ texture.data() })); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(rt->build()); + + QRhiCommandBuffer *cb = nullptr; + QVERIFY(rhi->beginOffscreenFrame(&cb) == QRhi::FrameOpSuccess); + QVERIFY(cb); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float verticesUvs[] = { + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(verticesUvs))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), verticesUvs); + + // There will be two renderpasses. One renders with no transformation and + // an opacity of 0.5, the second has a rotation. Bake the uniform data for + // both into a single buffer. + + const int UNIFORM_BLOCK_SIZE = 64 + 4; // matrix + opacity + const int secondUbufOffset = rhi->ubufAligned(UNIFORM_BLOCK_SIZE); + const int UBUF_SIZE = secondUbufOffset + UNIFORM_BLOCK_SIZE; + + QScopedPointer<QRhiBuffer> ubuf(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, UBUF_SIZE)); + QVERIFY(ubuf->build()); + + QMatrix4x4 matrix; + updates->updateDynamicBuffer(ubuf.data(), 0, 64, matrix.constData()); + float opacity = 0.5f; + updates->updateDynamicBuffer(ubuf.data(), 64, 4, &opacity); + + // rotation by 45 degrees around the Z axis + matrix.rotate(45, 0, 0, 1); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset, 64, matrix.constData()); + updates->updateDynamicBuffer(ubuf.data(), secondUbufOffset + 64, 4, &opacity); + + QScopedPointer<QRhiTexture> inputTexture(rhi->newTexture(QRhiTexture::RGBA8, inputImage.size())); + QVERIFY(inputTexture->build()); + updates->uploadTexture(inputTexture.data(), inputImage); + + QScopedPointer<QRhiSampler> sampler(rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge)); + QVERIFY(sampler->build()); + + const QRhiShaderResourceBinding::StageFlags commonVisibility = QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage; + QScopedPointer<QRhiShaderResourceBindings> srb0(rhi->newShaderResourceBindings()); + srb0->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), 0, UNIFORM_BLOCK_SIZE), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb0->build()); + + QScopedPointer<QRhiShaderResourceBindings> srb1(rhi->newShaderResourceBindings()); + srb1->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, commonVisibility, ubuf.data(), secondUbufOffset, UNIFORM_BLOCK_SIZE), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, inputTexture.data(), sampler.data()) + }); + QVERIFY(srb1->build()); + QVERIFY(srb1->isLayoutCompatible(srb0.data())); // hence no need for a second pipeline + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip); + QShader vs = loadShader(":/data/textured.vert.qsb"); + QVERIFY(vs.isValid()); + QShaderDescription shaderDesc = vs.description(); + QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); + QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); + + QShader fs = loadShader(":/data/textured.frag.qsb"); + QVERIFY(fs.isValid()); + shaderDesc = fs.description(); + QVERIFY(!shaderDesc.uniformBlocks().isEmpty()); + QCOMPARE(shaderDesc.uniformBlocks().first().size, UNIFORM_BLOCK_SIZE); + + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 4 * sizeof(float) } }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, + { 0, 1, QRhiVertexInputAttribute::Float2, 2 * sizeof(float) } + }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb0.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }, updates); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(); + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult0; + QImage result0; + readResult0.completed = [&readResult0, &result0] { + result0 = QImage(reinterpret_cast<const uchar *>(readResult0.data.constData()), + readResult0.pixelSize.width(), readResult0.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult0); + cb->endPass(readbackBatch); + + // second pass (rotated) + cb->beginPass(rt.data(), Qt::black, { 1.0f, 0 }); + cb->setGraphicsPipeline(pipeline.data()); + cb->setShaderResources(srb1.data()); // sources data from a different offset in ubuf + cb->setViewport({ 0, 0, float(texture->pixelSize().width()), float(texture->pixelSize().height()) }); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(4); + + QRhiReadbackResult readResult1; + QImage result1; + readResult1.completed = [&readResult1, &result1] { + result1 = QImage(reinterpret_cast<const uchar *>(readResult1.data.constData()), + readResult1.pixelSize.width(), readResult1.pixelSize.height(), + QImage::Format_RGBA8888_Premultiplied); + }; + readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({ texture.data() }, &readResult1); + cb->endPass(readbackBatch); + + rhi->endOffscreenFrame(); + + QVERIFY(!result0.isNull()); + QVERIFY(!result1.isNull()); + + if (rhi->isYUpInFramebuffer() != rhi->isYUpInNDC()) { + result0 = std::move(result0).mirrored(); + result1 = std::move(result1).mirrored(); + } + + if (impl == QRhi::Null) + return; + + // opacity 0.5 (premultiplied) + static const auto checkSemiWhite = [](const QRgb &c) { + QRgb semiWhite127 = qPremultiply(qRgba(255, 255, 255, 127)); + QRgb semiWhite128 = qPremultiply(qRgba(255, 255, 255, 128)); + return c == semiWhite127 || c == semiWhite128; + }; + QVERIFY(checkSemiWhite(result0.pixel(79, 77))); + QVERIFY(checkSemiWhite(result0.pixel(124, 81))); + QVERIFY(checkSemiWhite(result0.pixel(128, 149))); + QVERIFY(checkSemiWhite(result0.pixel(120, 189))); + QVERIFY(checkSemiWhite(result0.pixel(116, 185))); + QVERIFY(checkSemiWhite(result0.pixel(191, 172))); + + QRgb empty = qRgba(0, 0, 0, 0); + QCOMPARE(result0.pixel(11, 45), empty); + QCOMPARE(result0.pixel(246, 202), empty); + QCOMPARE(result0.pixel(130, 18), empty); + QCOMPARE(result0.pixel(4, 227), empty); + + // also rotated 45 degrees around Z + QRgb black = qRgba(0, 0, 0, 255); + QCOMPARE(result1.pixel(20, 23), black); + QCOMPARE(result1.pixel(47, 5), black); + QCOMPARE(result1.pixel(238, 22), black); + QCOMPARE(result1.pixel(250, 203), black); + QCOMPARE(result1.pixel(224, 237), black); + QCOMPARE(result1.pixel(12, 221), black); + + QVERIFY(checkSemiWhite(result1.pixel(142, 67))); + QVERIFY(checkSemiWhite(result1.pixel(81, 79))); + QVERIFY(checkSemiWhite(result1.pixel(79, 168))); + QVERIFY(checkSemiWhite(result1.pixel(146, 204))); + QVERIFY(checkSemiWhite(result1.pixel(186, 156))); + + QCOMPARE(result1.pixel(204, 45), empty); + QCOMPARE(result1.pixel(28, 178), empty); +} + +void tst_QRhi::renderToWindowSimple_data() +{ + rhiTestData(); +} + +void tst_QRhi::renderToWindowSimple() +{ + QFETCH(QRhi::Implementation, impl); + QFETCH(QRhiInitParams *, initParams); + +#ifdef Q_OS_WINRT + if (impl == QRhi::D3D11) + QSKIP("Skipping window-based QRhi rendering on WinRT as the platform and the D3D11 backend are not prepared for this yet"); +#endif + + QScopedPointer<QRhi> rhi(QRhi::create(impl, initParams, QRhi::Flags(), nullptr)); + if (!rhi) + QSKIP("QRhi could not be created, skipping testing rendering"); + + QScopedPointer<QWindow> window(new QWindow); + switch (impl) { + case QRhi::OpenGLES2: + Q_FALLTHROUGH(); + case QRhi::D3D11: + window->setSurfaceType(QSurface::OpenGLSurface); + break; + case QRhi::Metal: + window->setSurfaceType(QSurface::MetalSurface); + break; + case QRhi::Vulkan: + window->setSurfaceType(QSurface::VulkanSurface); +#if QT_CONFIG(vulkan) + window->setVulkanInstance(&vulkanInstance); +#endif + break; + default: + break; + } + + window->setGeometry(0, 0, 640, 480); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QScopedPointer<QRhiSwapChain> swapChain(rhi->newSwapChain()); + swapChain->setWindow(window.data()); + swapChain->setFlags(QRhiSwapChain::UsedAsTransferSource); + QScopedPointer<QRhiRenderPassDescriptor> rpDesc(swapChain->newCompatibleRenderPassDescriptor()); + swapChain->setRenderPassDescriptor(rpDesc.data()); + QVERIFY(swapChain->buildOrResize()); + + QRhiResourceUpdateBatch *updates = rhi->nextResourceUpdateBatch(); + + static const float vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + 0.0f, 1.0f + }; + QScopedPointer<QRhiBuffer> vbuf(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertices))); + QVERIFY(vbuf->build()); + updates->uploadStaticBuffer(vbuf.data(), vertices); + + QScopedPointer<QRhiShaderResourceBindings> srb(rhi->newShaderResourceBindings()); + QVERIFY(srb->build()); + + QScopedPointer<QRhiGraphicsPipeline> pipeline(rhi->newGraphicsPipeline()); + QShader vs = loadShader(":/data/simple.vert.qsb"); + QVERIFY(vs.isValid()); + QShader fs = loadShader(":/data/simple.frag.qsb"); + QVERIFY(fs.isValid()); + pipeline->setShaderStages({ { QRhiShaderStage::Vertex, vs }, { QRhiShaderStage::Fragment, fs } }); + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ { 2 * sizeof(float) } }); + inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } }); + pipeline->setVertexInputLayout(inputLayout); + pipeline->setShaderResourceBindings(srb.data()); + pipeline->setRenderPassDescriptor(rpDesc.data()); + + QVERIFY(pipeline->build()); + + const int framesInFlight = rhi->resourceLimit(QRhi::FramesInFlight); + QVERIFY(framesInFlight >= 1); + const int FRAME_COUNT = framesInFlight + 1; + bool readCompleted = false; + QRhiReadbackResult readResult; + QImage result; + int readbackWidth = 0; + + for (int frameNo = 0; frameNo < FRAME_COUNT; ++frameNo) { + QVERIFY(rhi->beginFrame(swapChain.data()) == QRhi::FrameOpSuccess); + QRhiCommandBuffer *cb = swapChain->currentFrameCommandBuffer(); + QRhiRenderTarget *rt = swapChain->currentFrameRenderTarget(); + const QSize outputSize = swapChain->currentPixelSize(); + QCOMPARE(rt->pixelSize(), outputSize); + QRhiViewport viewport(0, 0, float(outputSize.width()), float(outputSize.height())); + + cb->beginPass(rt, Qt::blue, { 1.0f, 0 }, updates); + updates = nullptr; + cb->setGraphicsPipeline(pipeline.data()); + cb->setViewport(viewport); + QRhiCommandBuffer::VertexInput vbindings(vbuf.data(), 0); + cb->setVertexInput(0, 1, &vbindings); + cb->draw(3); + + if (frameNo == 0) { + readResult.completed = [&readCompleted, &readResult, &result, &rhi] { + readCompleted = true; + QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()), + readResult.pixelSize.width(), readResult.pixelSize.height(), + QImage::Format_ARGB32_Premultiplied); + if (readResult.format == QRhiTexture::RGBA8) + wrapperImage = wrapperImage.rgbSwapped(); + if (rhi->isYUpInFramebuffer() == rhi->isYUpInNDC()) + result = wrapperImage.mirrored(); + else + result = wrapperImage.copy(); + }; + QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch(); + readbackBatch->readBackTexture({}, &readResult); // read back the current backbuffer + readbackWidth = outputSize.width(); + cb->endPass(readbackBatch); + } else { + cb->endPass(); + } + + rhi->endFrame(swapChain.data()); + } + + // The readback is asynchronous here. However it is guaranteed that it + // finished at latest after rendering QRhi::FramesInFlight frames after the + // one that enqueues the readback. + QVERIFY(readCompleted); + QVERIFY(readbackWidth > 0); + + if (impl == QRhi::Null) + return; + + // Now we have a red rectangle on blue background. + const int y = 50; + const quint32 *p = reinterpret_cast<const quint32 *>(result.constScanLine(y)); + int x = result.width() - 1; + int redCount = 0; + int blueCount = 0; + const int maxFuzz = 1; + while (x-- >= 0) { + const QRgb c(*p++); + if (qRed(c) >= (255 - maxFuzz) && qGreen(c) == 0 && qBlue(c) == 0) + ++redCount; + else if (qRed(c) == 0 && qGreen(c) == 0 && qBlue(c) >= (255 - maxFuzz)) + ++blueCount; + else + QFAIL("Encountered a pixel that is neither red or blue"); + } + + QCOMPARE(redCount + blueCount, readbackWidth); + QVERIFY(redCount < blueCount); +} + #include <tst_qrhi.moc> QTEST_MAIN(tst_QRhi) diff --git a/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp b/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp index 5a60c78cb5..7b73e6e952 100644 --- a/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp +++ b/tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp @@ -29,7 +29,7 @@ #include <QXmlStreamReader> extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { - QXmlStreamReader reader(QByteArray(Data, Size)); + QXmlStreamReader reader(QByteArray::fromRawData(Data, Size)); while (!reader.atEnd()) reader.readNext(); return 0; diff --git a/tests/libfuzzer/gui/iccparser/main.cpp b/tests/libfuzzer/gui/iccparser/main.cpp index ba4f70ef3b..1db43d2e25 100644 --- a/tests/libfuzzer/gui/iccparser/main.cpp +++ b/tests/libfuzzer/gui/iccparser/main.cpp @@ -32,6 +32,6 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) { static int c = 0; static QGuiApplication a(c, nullptr); - QColorSpace cs = QColorSpace::fromIccProfile(QByteArray(data, size)); + QColorSpace cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(data, size)); return 0; } diff --git a/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp b/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp index c9b33d0f88..51fa3c9e0f 100644 --- a/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp +++ b/tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp @@ -32,6 +32,6 @@ extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { static int c = 0; static QApplication a(c, nullptr); - QTextDocument().setHtml(QByteArray(Data, Size)); + QTextDocument().setHtml(QByteArray::fromRawData(Data, Size)); return 0; } diff --git a/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp b/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp index acdd91e06e..66ddf738f2 100644 --- a/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp +++ b/tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp @@ -29,6 +29,6 @@ #include <QTextDocument> extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { - QTextDocument().setMarkdown(QByteArray(Data, Size)); + QTextDocument().setMarkdown(QByteArray::fromRawData(Data, Size)); return 0; } |