Commit a3572d7e authored by Laszlo Agocs's avatar Laszlo Agocs

vk: Partially implement readbacks

parent e356ce28
......@@ -49,6 +49,8 @@
****************************************************************************/
#include <QGuiApplication>
#include <QImage>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QRhiVulkanInitParams>
......@@ -87,7 +89,7 @@ int main(int argc, char **argv)
return 1;
}
QRhiTexture *tex = r->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), QRhiTexture::RenderTarget);
QRhiTexture *tex = r->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), QRhiTexture::RenderTarget | QRhiTexture::ReadBack);
tex->build();
QRhiTextureRenderTarget *rt = r->newTextureRenderTarget({ tex });
QRhiRenderPassDescriptor *rp = rt->newCompatibleRenderPassDescriptor();
......@@ -105,6 +107,7 @@ int main(int argc, char **argv)
QRhiReadbackDescription rb(tex);
QRhiReadbackResult rbResult;
rbResult.completed = [frame] { qDebug(" - readback %d completed", frame); };
r->readback(cb, rb, &rbResult);
qDebug("Submit and wait");
......@@ -112,7 +115,16 @@ int main(int argc, char **argv)
// No finish() or waiting for the completed callback is needed here
// since the endOffscreenFrame() implies a wait for completion.
qDebug() << rbResult.data.size();
if (!rbResult.data.isEmpty()) {
const uchar *p = reinterpret_cast<const uchar *>(rbResult.data.constData());
QImage image(p, 1280, 720, QImage::Format_RGBA8888);
QString fn = QString::asprintf("frame%d.png", frame);
fn = QFileInfo(fn).absoluteFilePath();
qDebug("Saving into %s", qPrintable(fn));
image.save(fn);
} else {
qWarning("Readback failed!");
}
}
rt->releaseAndDestroy();
......
......@@ -425,9 +425,9 @@ QRhi::FrameOpResult QRhi::endOffscreenFrame()
return d->endOffscreenFrame();
}
void QRhi::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
bool QRhi::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
{
d->readback(cb, rb, result);
return d->readback(cb, rb, result);
}
QRhi::FrameOpResult QRhi::finish()
......
......@@ -396,7 +396,8 @@ public:
ChangesFrequently = 1 << 1, // hint for backend to keep staging resources around
CubeMap = 1 << 2,
MipMapped = 1 << 3,
sRGB = 1 << 4
sRGB = 1 << 4,
ReadBack = 1 << 5
};
Q_DECLARE_FLAGS(Flags, Flag)
......@@ -1004,7 +1005,7 @@ public:
FrameOpResult endOffscreenFrame();
// Cannot be inside a pass.
void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result);
bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result);
// Waits for any work on the graphics queue (where applicable) to complete,
// then forcibly executes all deferred operations, like completing
......
......@@ -85,7 +85,7 @@ public:
virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain) = 0;
virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) = 0;
virtual QRhi::FrameOpResult endOffscreenFrame() = 0;
virtual void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) = 0;
virtual bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) = 0;
virtual QRhi::FrameOpResult finish() = 0;
virtual void beginPass(QRhiRenderTarget *rt,
......
......@@ -508,13 +508,15 @@ QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame()
return QRhi::FrameOpError;
}
void QRhiD3D11::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
bool QRhiD3D11::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
{
Q_UNUSED(cb);
Q_UNUSED(rb);
Q_UNUSED(result);
Q_ASSERT(inFrame && !inPass);
return false;
}
QRhi::FrameOpResult QRhiD3D11::finish()
......
......@@ -376,7 +376,7 @@ public:
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain) override;
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
QRhi::FrameOpResult endOffscreenFrame() override;
void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
QRhi::FrameOpResult finish() override;
void beginPass(QRhiRenderTarget *rt,
......
......@@ -410,13 +410,15 @@ QRhi::FrameOpResult QRhiGles2::endOffscreenFrame()
return QRhi::FrameOpError;
}
void QRhiGles2::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
bool QRhiGles2::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
{
Q_UNUSED(cb);
Q_UNUSED(rb);
Q_UNUSED(result);
Q_ASSERT(inFrame && !inPass);
return false;
}
QRhi::FrameOpResult QRhiGles2::finish()
......
......@@ -334,7 +334,7 @@ public:
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain) override;
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
QRhi::FrameOpResult endOffscreenFrame() override;
void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
QRhi::FrameOpResult finish() override;
void beginPass(QRhiRenderTarget *rt,
......
......@@ -568,13 +568,15 @@ QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
return QRhi::FrameOpError;
}
void QRhiMetal::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
bool QRhiMetal::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
{
Q_UNUSED(cb);
Q_UNUSED(rb);
Q_UNUSED(result);
Q_ASSERT(inFrame && !inPass);
return false;
}
QRhi::FrameOpResult QRhiMetal::finish()
......
......@@ -265,7 +265,7 @@ public:
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain) override;
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
QRhi::FrameOpResult endOffscreenFrame() override;
void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
QRhi::FrameOpResult finish() override;
void beginPass(QRhiRenderTarget *rt,
......
......@@ -1382,20 +1382,68 @@ QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame()
return QRhi::FrameOpSuccess;
}
void QRhiVulkan::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
bool QRhiVulkan::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
{
Q_UNUSED(cb);
Q_ASSERT(inFrame && !inPass);
// create a host visible buffer
// add a vkCmdCopyImageToBuffer
QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
ActiveReadback aRb;
aRb.activeFrameSlot = currentFrameSlot;
aRb.desc = rb;
aRb.result = result;
QVkTexture *tex = QRHI_RES(QVkTexture, aRb.desc.texture);
// ###
aRb.bufSize = tex->m_pixelSize.width() * tex->m_pixelSize.height() * 4;
// Create a host visible buffer.
VkBufferCreateInfo bufferInfo;
memset(&bufferInfo, 0, sizeof(bufferInfo));
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = aRb.bufSize;
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, &aRb.buf, &allocation, nullptr);
if (err == VK_SUCCESS) {
aRb.bufAlloc = allocation;
} else {
qWarning("Failed to create readback buffer of size %d: %d", aRb.bufSize, err);
return false;
}
// Copy from the (optimal and not host visible) image into the buffer.
VkBufferImageCopy copyDesc;
memset(&copyDesc, 0, sizeof(copyDesc));
copyDesc.bufferOffset = 0;
copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; // ### no depth for now
copyDesc.imageSubresource.mipLevel = aRb.desc.level;
copyDesc.imageSubresource.baseArrayLayer = aRb.desc.layer;
copyDesc.imageSubresource.layerCount = 1;
copyDesc.imageExtent.width = tex->m_pixelSize.width();
copyDesc.imageExtent.height = tex->m_pixelSize.height();
copyDesc.imageExtent.depth = 1;
// assume the image was written
imageBarrier(cb, tex,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
df->vkCmdCopyImageToBuffer(cbD->cb, tex->image, tex->layout, aRb.buf, 1, &copyDesc);
// behave as if the image was used for shader read
imageBarrier(cb, tex,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
activeReadbacks.append(aRb);
return true;
}
QRhi::FrameOpResult QRhiVulkan::finish()
......@@ -2026,8 +2074,18 @@ void QRhiVulkan::finishActiveReadbacks(bool forced)
for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]);
if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
// map and memcpy
// destroy temp buffer
aRb.result->data.resize(aRb.bufSize);
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;
}
memcpy(aRb.result->data.data(), p, aRb.bufSize);
vmaUnmapMemory(toVmaAllocator(allocator), a);
vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a);
if (aRb.result->completed)
completedCallbacks.append(aRb.result->completed);
......@@ -2925,6 +2983,8 @@ bool QVkTexture::build()
else
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
}
if (m_flags.testFlag(QRhiTexture::ReadBack))
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocInfo;
memset(&allocInfo, 0, sizeof(allocInfo));
......
......@@ -327,7 +327,7 @@ public:
QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain) override;
QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
QRhi::FrameOpResult endOffscreenFrame() override;
void readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
bool readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &rb, QRhiReadbackResult *result) override;
QRhi::FrameOpResult finish() override;
void beginPass(QRhiRenderTarget *rt,
......@@ -473,6 +473,9 @@ public:
int activeFrameSlot = -1;
QRhiReadbackDescription desc;
QRhiReadbackResult *result;
VkBuffer buf;
QVkAlloc bufAlloc;
int bufSize;
};
QVector<ActiveReadback> activeReadbacks;
......
gl, mtl: compressed textures
gl, mtl: srgb (tex, swapchain buf)
rhi without a window, fully offscreen
readback api
some wait-gpu stuff for waiting for readback results inside a begin-endFrame f.ex.
vk: implement finish()
vk: readback
mtl, d3d, gl: rhi without a window
mtl, d3d, gl: implement finish()
mtl, d3d, gl: readback
gl, mtl: compressed textures
gl, mtl: srgb (tex, swapchain buf)
multi window? (multi swapchain) -> trouble
vk: rendering hangs sometimes when minimize and back on some systems?
mtl: cubemaps
......@@ -36,6 +32,8 @@ gl: tex size stuff (npot etc.)
gl: tex formats
more QImage->tex formats
vk compressed tex: could it consume a complete ktx without any memcpys?
multi-buffer (region) readback?
depth readback?
d3d, gl, mtl: cache shader sources?
advanced blend modes?
resize to 0 width or height fails (vk)
......@@ -53,6 +51,10 @@ dxc for d3d as an alternative to fxc?
hlsl -> dxc -> spirv -> spirv-cross hmmm...
+++ done
vk: implement finish()
some wait-gpu stuff for waiting for readback results inside a begin-endFrame f.ex.
readback api
rhi without a window, fully offscreen
vk: compressed textures (etc2, astc, bc)
vk: srgb (tex, swapchain buf)
d3d: srgb support (texture, swapchain buf)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment