rhi: Add support for buffer readbacks

This also marks the beginnings of significantly extending autotesting
of the resource and rendering functionality in QRhi.

Also involves fixing up the buffer operation lists like we did
for textures before. This is to ensure updates and reads on the
same batch execute in the correct order. So just have two lists:
one with buffer, one with texture operations.

Also simplify the struct layouts. No need for those inner structs
with many duplicate members. This reduces the size even, since using a
union was never an option here. Also switch to a VLA, the size is around
253 KB per batch.

The Null backend now keeps track of the QRhiBuffer data so it can return
valid results in readbacks.

Task-number: QTBUG-78984
Task-number: QTBUG-78986
Task-number: QTBUG-78971
Task-number: QTBUG-78883
Change-Id: I9694bd7fec523a4e71cf8a5c77c828123ebbb3bd
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
bb10
Laszlo Agocs 2019-10-03 14:28:23 +02:00
parent 4a3ee77f65
commit 89ec1b3618
13 changed files with 856 additions and 437 deletions

View File

@ -566,6 +566,13 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\value TriangleFanTopology Indicates that QRhiGraphicsPipeline::setTopology()
supports QRhiGraphicsPipeline::TriangleFan.
\value ReadBackNonUniformBuffer Indicates that
{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 can be
unsupported, for example, on OpenGL ES 2.0 implementations without the
MapBuffer extension.
*/
/*!
@ -4175,7 +4182,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 +4196,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 +4206,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 +4240,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 +4265,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 +4317,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 +4329,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 +4378,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 +4387,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);
}
/*!

View File

@ -1355,6 +1355,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 +1373,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 +1435,8 @@ public:
VertexShaderPointSize,
BaseVertex,
BaseInstance,
TriangleFanTopology
TriangleFanTopology,
ReadBackNonUniformBuffer
};
enum BeginFrameFlag {

View File

@ -218,7 +218,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 +271,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 +321,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;
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 textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
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 +388,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>

View File

@ -471,6 +471,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TriangleFanTopology:
return false;
case QRhi::ReadBackNonUniformBuffer:
return true;
default:
Q_UNREACHABLE();
return false;
@ -1323,54 +1325,97 @@ 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::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;
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);
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 size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize();
const QPoint sp = u.desc.sourceTopLeft();
D3D11_BOX srcBox;
srcBox.left = UINT(sp.x());
srcBox.top = UINT(sp.y());
@ -1391,16 +1436,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 +1455,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 = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize) : 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 +1480,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 +1502,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 +1515,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 +1539,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)

View File

@ -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

View File

@ -492,6 +492,8 @@ bool QRhiGles2::create(QRhi::Flags flags)
else
caps.textureCompareMode = true;
caps.mapBuffer = f->hasOpenGLExtension(QOpenGLExtensions::MapBuffer);
if (!caps.gles) {
f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
f->glEnable(GL_POINT_SPRITE);
@ -734,6 +736,8 @@ 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.mapBuffer;
default:
Q_UNREACHABLE();
return false;
@ -1417,65 +1421,82 @@ 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 size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : 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,16 +1506,16 @@ 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();
@ -1505,8 +1526,8 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
} 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;
@ -1516,12 +1537,12 @@ void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
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 +2101,32 @@ 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.mapBuffer) {
void *p = f->glMapBuffer(cmd.args.getBufferSubData.target, GL_READ_ONLY);
if (p) {
result->data.resize(cmd.args.getBufferSubData.size);
memcpy(result->data.data(),
reinterpret_cast<char *>(p) + cmd.args.getBufferSubData.offset,
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;

View File

@ -312,8 +312,8 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
BindShaderResources,
BindFramebuffer,
Clear,
BufferData,
BufferSubData,
GetBufferSubData,
CopyTex,
ReadPixels,
SubImage,
@ -401,6 +401,13 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
int size;
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;
@ -744,7 +751,9 @@ public:
rgba8Format(false),
instancing(false),
baseVertex(false),
compute(false)
compute(false),
textureCompareMode(false),
mapBuffer(false)
{ }
int ctxMajor;
int ctxMinor;
@ -775,6 +784,7 @@ public:
uint baseVertex : 1;
uint compute : 1;
uint textureCompareMode : 1;
uint mapBuffer : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QVector<GLint> supportedCompressedFormats;

View File

@ -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
@ -552,6 +552,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TriangleFanTopology:
return false;
case QRhi::ReadBackNonUniformBuffer:
return true;
default:
Q_UNREACHABLE();
return false;
@ -1541,21 +1543,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 +1583,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 +1602,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 +1617,32 @@ 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 size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : 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)
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 +1651,17 @@ 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)
readback.pixelSize = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize)
: texD->m_pixelSize;
aRb.format = texD->m_format;
readback.format = texD->m_format;
src = texD->d->tex;
srcSize = texD->m_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 +1670,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 +1711,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 +1728,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 +1964,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 +2055,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()];

View File

@ -389,13 +389,28 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re
{
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();
QRhiReadbackResult *result = u.result;
QRhiTexture *tex = u.rb.texture();
if (tex) {
result->format = tex->format();
result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize());
result->pixelSize = q->sizeForMipLevel(u.rb.level(), tex->pixelSize());
} else {
Q_ASSERT(currentSwapChain);
result->format = QRhiTexture::RGBA8;
@ -403,7 +418,7 @@ void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *re
}
quint32 byteSize = 0;
textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize);
result->data.fill(0, byteSize);
result->data.fill(0, int(byteSize));
if (result->completed)
result->completed();
}
@ -454,14 +469,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;
}

View File

@ -59,6 +59,8 @@ struct QNullBuffer : public QRhiBuffer
~QNullBuffer();
void release() override;
bool build() override;
QByteArray data;
};
struct QNullRenderBuffer : public QRhiRenderBuffer

View File

@ -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();
@ -2645,100 +2650,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);
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);
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;
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;
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;
}
}
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);
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()));
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()));
trackedBufferBarrier(cbD, bufD, 0,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
trackedBufferBarrier(cbD, bufD, 0,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferCopy copyInfo;
memset(&copyInfo, 0, sizeof(copyInfo));
copyInfo.srcOffset = VkDeviceSize(u.offset);
copyInfo.dstOffset = VkDeviceSize(u.offset);
copyInfo.size = VkDeviceSize(u.data.size());
VkBufferCopy copyInfo;
memset(&copyInfo, 0, sizeof(copyInfo));
copyInfo.srcOffset = VkDeviceSize(u.offset);
copyInfo.dstOffset = VkDeviceSize(u.offset);
copyInfo.size = VkDeviceSize(u.data.size());
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);
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);
// 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.
// 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.
bufD->lastActiveFrameSlot = currentFrameSlot;
bufD->lastActiveFrameSlot = currentFrameSlot;
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;
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));
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;
}
trackedBufferBarrier(cbD, bufD, 0, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferCopy copyInfo;
memset(&copyInfo, 0, sizeof(copyInfo));
copyInfo.srcOffset = VkDeviceSize(u.offset);
copyInfo.size = VkDeviceSize(u.readSize);
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);
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);
}
}
@ -2776,7 +2845,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)) {
@ -2817,34 +2886,34 @@ 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();
const QSize size = u.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.desc.pixelSize();
region.extent.width = uint32_t(size.width());
region.extent.height = uint32_t(size.height());
region.extent.depth = 1;
@ -2865,21 +2934,21 @@ 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 = u.rb.level() > 0 ? q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize)
: texD->m_pixelSize;
readback.format = texD->m_format;
texD->lastActiveFrameSlot = currentFrameSlot;
} else {
Q_ASSERT(currentSwapChain);
@ -2888,21 +2957,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;
@ -2910,14 +2979,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;
}
@ -2926,11 +2995,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) {
@ -2940,7 +3009,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 {
@ -2965,14 +3034,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();
@ -2989,14 +3058,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);
}
@ -3004,7 +3073,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;
@ -3012,7 +3081,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);
@ -3021,7 +3090,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);
@ -3047,13 +3116,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);
}
@ -3066,8 +3135,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);
@ -3083,7 +3151,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)
@ -3095,7 +3163,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)
@ -3189,29 +3257,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);
}
}
@ -3729,6 +3821,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::TriangleFanTopology:
return true;
case QRhi::ReadBackNonUniformBuffer:
return true;
default:
Q_UNREACHABLE();
return false;
@ -4898,7 +4992,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);
@ -4912,11 +5006,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);
}

View File

@ -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

View File

@ -71,6 +71,8 @@ private slots:
void create();
void nativeHandles_data();
void nativeHandles();
void resourceUpdateBatchBuffer_data();
void resourceUpdateBatchBuffer();
private:
struct {
@ -103,9 +105,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()
@ -248,7 +267,9 @@ void tst_QRhi::create()
QRhi::WideLines,
QRhi::VertexShaderPointSize,
QRhi::BaseVertex,
QRhi::BaseInstance
QRhi::BaseInstance,
QRhi::TriangleFanTopology,
QRhi::ReadBackNonUniformBuffer
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
@ -484,5 +505,91 @@ void tst_QRhi::nativeHandles()
}
}
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 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);
QRhiCommandBuffer *cb = nullptr;
QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
QVERIFY(result == QRhi::FrameOpSuccess);
QVERIFY(cb);
cb->resourceUpdate(batch);
rhi->endOffscreenFrame();
// 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);
QRhiCommandBuffer *cb = nullptr;
QRhi::FrameOpResult result = rhi->beginOffscreenFrame(&cb);
QVERIFY(result == QRhi::FrameOpSuccess);
QVERIFY(cb);
cb->resourceUpdate(batch);
rhi->endOffscreenFrame();
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");
}
}
}
#include <tst_qrhi.moc>
QTEST_MAIN(tst_QRhi)