summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-10-11 01:01:14 +0200
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-10-11 01:01:15 +0200
commit50d41f14eecc32e374ca34f76d45d65684a640c8 (patch)
tree1c38a164c1604139103aa69d88e8d184112f1b11
parente1fd6074935cd0be0df14d89eb0244fcbd1677cf (diff)
parent5e421957de544224511c4823a4829ed2a34317d0 (diff)
Merge remote-tracking branch 'origin/5.15' into dev
-rw-r--r--qmake/generators/metamakefile.cpp3
-rw-r--r--src/concurrent/qtconcurrentmapkernel.h20
-rw-r--r--src/corelib/global/qlibraryinfo.cpp5
-rw-r--r--src/gui/rhi/qrhi.cpp110
-rw-r--r--src/gui/rhi/qrhi_p.h12
-rw-r--r--src/gui/rhi/qrhi_p_p.h133
-rw-r--r--src/gui/rhi/qrhid3d11.cpp233
-rw-r--r--src/gui/rhi/qrhid3d11_p_p.h15
-rw-r--r--src/gui/rhi/qrhigles2.cpp202
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h16
-rw-r--r--src/gui/rhi/qrhimetal.mm165
-rw-r--r--src/gui/rhi/qrhinull.cpp145
-rw-r--r--src/gui/rhi/qrhinull_p_p.h7
-rw-r--r--src/gui/rhi/qrhivulkan.cpp397
-rw-r--r--src/gui/rhi/qrhivulkan_p_p.h23
-rw-r--r--src/network/configure.json12
-rw-r--r--src/network/kernel/qauthenticator.cpp6
-rw-r--r--src/platformsupport/input/evdevtouch/qevdevtouchhandler.cpp13
-rw-r--r--src/widgets/kernel/qwidget.cpp8
-rw-r--r--tests/auto/corelib/text/qstring/tst_qstring.cpp3
-rw-r--r--tests/auto/gui/rhi/qrhi/data/compile.bat48
-rw-r--r--tests/auto/gui/rhi/qrhi/data/qt256.pngbin0 -> 6208 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag8
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.frag.qsbbin0 -> 908 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert10
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simple.vert.qsbbin0 -> 958 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.frag13
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsbbin0 -> 1479 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.vert14
-rw-r--r--tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsbbin0 -> 1195 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/texture.frag12
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.frag19
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.frag.qsbbin0 -> 1997 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.vert (renamed from tests/auto/gui/rhi/qrhi/data/texture.vert)9
-rw-r--r--tests/auto/gui/rhi/qrhi/data/textured.vert.qsbbin0 -> 1708 bytes
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp1487
-rw-r--r--tests/auto/other/other.pro2
-rw-r--r--tests/libfuzzer/corelib/serialization/qxmlstream/qxmlstreamreader/readnext/main.cpp2
-rw-r--r--tests/libfuzzer/gui/iccparser/main.cpp2
-rw-r--r--tests/libfuzzer/gui/text/qtextdocument/setHtml/main.cpp2
-rw-r--r--tests/libfuzzer/gui/text/qtextdocument/setMarkdown/main.cpp2
41 files changed, 2647 insertions, 511 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/corelib/global/qlibraryinfo.cpp b/src/corelib/global/qlibraryinfo.cpp
index 434b657df3..3e56e258b9 100644
--- a/src/corelib/global/qlibraryinfo.cpp
+++ b/src/corelib/global/qlibraryinfo.cpp
@@ -727,6 +727,11 @@ QLibraryInfo::rawLocation(LibraryLocation loc, PathGroup group)
#ifndef QT_BUILD_QMAKE_BOOTSTRAP
if (!fromConf) {
+ // "volatile" here is a hack to prevent compilers from doing a
+ // compile-time strlen() on "path". The issue is that Qt installers
+ // will binary-patch the Qt installation paths -- in such scenarios, Qt
+ // will be built with a dummy path, thus the compile-time result of
+ // strlen is meaningless.
const char * volatile path = 0;
if (loc == PrefixPath) {
path = getPrefix(
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(&copyInfo, 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(&copyInfo, 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(&copyInfo, 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(&region, 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(&copyDesc, 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 ea9299f36e..49636fa5d1 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -9213,9 +9213,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
new file mode 100644
index 0000000000..30c621c9c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/qt256.png
Binary files differ
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
new file mode 100644
index 0000000000..264b71ec0f
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.frag.qsb
Binary files differ
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
new file mode 100644
index 0000000000..59080b60c6
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simple.vert.qsb
Binary files differ
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
new file mode 100644
index 0000000000..f302702aa9
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.frag.qsb
Binary files differ
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
new file mode 100644
index 0000000000..e4f12bfb9e
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/simpletextured.vert.qsb
Binary files differ
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
new file mode 100644
index 0000000000..0a039137ec
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/textured.frag.qsb
Binary files differ
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
new file mode 100644
index 0000000000..7853f77943
--- /dev/null
+++ b/tests/auto/gui/rhi/qrhi/data/textured.vert.qsb
Binary files differ
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/auto/other/other.pro b/tests/auto/other/other.pro
index c5426202e8..4206852e4c 100644
--- a/tests/auto/other/other.pro
+++ b/tests/auto/other/other.pro
@@ -11,7 +11,7 @@ SUBDIRS=\
macplist \
networkselftest \
qaccessibility \
- # qaccessibilitylinux \ # QTBUG-44434
+ # qaccessibilitylinux # QTBUG-44434 \
qaccessibilitymac \
qcomplextext \
qfocusevent \
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;
}