Commit 514b98f6 authored by Laszlo Agocs's avatar Laszlo Agocs

vk: Support reading back from the back buffer

parent 411a9b7b
......@@ -204,7 +204,7 @@ int main(int argc, char **argv)
// since the endOffscreenFrame() implies a wait for completion.
if (!rbResult.data.isEmpty()) {
const uchar *p = reinterpret_cast<const uchar *>(rbResult.data.constData());
QImage image(p, 1280, 720, QImage::Format_RGBA8888);
QImage image(p, rbResult.pixelSize.width(), rbResult.pixelSize.height(), QImage::Format_RGBA8888);
QString fn = QString::asprintf("frame%d.png", frame);
fn = QFileInfo(fn).absoluteFilePath();
qDebug("Saving into %s", qPrintable(fn));
......
......@@ -49,8 +49,10 @@
****************************************************************************/
#include "examplewindow.h"
#include <QFileInfo>
//#define USE_SRGB_SWAPCHAIN
//#define READBACK_SWAPCHAIN
void ExampleWindow::exposeEvent(QExposeEvent *)
{
......@@ -254,7 +256,29 @@ void ExampleWindow::render()
m_liveTexCubeRenderer.queueDraw(cb, outputSize);
m_r->endPass(cb);
#ifdef READBACK_SWAPCHAIN
QRhiReadbackDescription rb; // no texture given -> backbuffer
QRhiReadbackResult *rbResult = new QRhiReadbackResult;
int frameNo = m_frameCount;
rbResult->completed = [this, rbResult, frameNo] {
{
QImage::Format fmt = rbResult->format == QRhiTexture::BGRA8 ? QImage::Format_ARGB32_Premultiplied
: QImage::Format_RGBA8888_Premultiplied;
const uchar *p = reinterpret_cast<const uchar *>(rbResult->data.constData());
QImage image(p, rbResult->pixelSize.width(), rbResult->pixelSize.height(), fmt);
QString fn = QString::asprintf("frame%d.png", frameNo);
fn = QFileInfo(fn).absoluteFilePath();
qDebug("Saving into %s", qPrintable(fn));
image.save(fn);
}
delete rbResult;
};
m_r->readback(cb, rb, rbResult);
#endif
m_r->endFrame(m_sc);
++m_frameCount;
requestUpdate(); // render continuously, throttled by the presentation rate
}
......@@ -79,6 +79,7 @@ protected:
bool m_running = false;
bool m_notExposed = false;
bool m_newlyExposed = false;
int m_frameCount = 0;
QRhi *m_r = nullptr;
bool m_hasSwapChain = false;
......
......@@ -94,6 +94,13 @@ void TexturedCubeRenderer::initResources(QRhiRenderPassDescriptor *rp)
m_ps = m_r->newGraphicsPipeline();
// No blending but the texture has alpha which we do not want to write out.
// Be nice. (would not matter for an onscreen window but makes a difference
// when reading back and saving into image files f.ex.)
QRhiGraphicsPipeline::TargetBlend blend;
blend.colorWrite = QRhiGraphicsPipeline::R | QRhiGraphicsPipeline::G | QRhiGraphicsPipeline::B;
m_ps->setTargetBlends({ blend });
m_ps->setDepthTest(true);
m_ps->setDepthWrite(true);
m_ps->setDepthOp(QRhiGraphicsPipeline::Less);
......
......@@ -283,8 +283,8 @@ Q_DECLARE_TYPEINFO(QRhiTextureUploadDescription, Q_MOVABLE_TYPE);
struct Q_RHI_EXPORT QRhiReadbackDescription
{
QRhiReadbackDescription() { }
QRhiReadbackDescription(QRhiTexture *texture_) : texture(texture_) { }
QRhiReadbackDescription() { } // source is the current back buffer (if swapchain supports readback)
QRhiReadbackDescription(QRhiTexture *texture_) : texture(texture_) { } // source is the specified texture
QRhiTexture *texture = nullptr;
int layer = 0;
int level = 0;
......@@ -292,12 +292,6 @@ struct Q_RHI_EXPORT QRhiReadbackDescription
Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_MOVABLE_TYPE);
struct Q_RHI_EXPORT QRhiReadbackResult
{
std::function<void()> completed = nullptr;
QByteArray data;
}; // non-movable due to the std::function
class Q_RHI_EXPORT QRhiResource
{
public:
......@@ -402,6 +396,8 @@ public:
Q_DECLARE_FLAGS(Flags, Flag)
enum Format {
UnknownFormat,
RGBA8,
BGRA8,
R8,
......@@ -883,6 +879,14 @@ private:
Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiResourceUpdateBatch::TexturePrepareFlags)
struct Q_RHI_EXPORT QRhiReadbackResult
{
std::function<void()> completed = nullptr;
QRhiTexture::Format format;
QSize pixelSize;
QByteArray data;
}; // non-movable due to the std::function
struct Q_RHI_EXPORT QRhiInitParams
{
};
......
......@@ -329,6 +329,7 @@ void QRhiVulkan::destroy()
df->vkDeviceWaitIdle(dev);
executeDeferredReleases(true);
finishActiveReadbacks(true);
if (ofr.cmdFence) {
df->vkDestroyFence(dev, ofr.cmdFence, nullptr);
......@@ -507,6 +508,36 @@ static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture
}
}
static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags)
{
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM:
return QRhiTexture::RGBA8;
case VK_FORMAT_R8G8B8A8_SRGB:
if (flags)
(*flags) |= QRhiTexture::sRGB;
return QRhiTexture::RGBA8;
case VK_FORMAT_B8G8R8A8_UNORM:
return QRhiTexture::BGRA8;
case VK_FORMAT_B8G8R8A8_SRGB:
if (flags)
(*flags) |= QRhiTexture::sRGB;
return QRhiTexture::BGRA8;
case VK_FORMAT_R8_UNORM:
return QRhiTexture::R8;
case VK_FORMAT_R8_SRGB:
if (flags)
(*flags) |= QRhiTexture::sRGB;
return QRhiTexture::R8;
case VK_FORMAT_R16_UNORM:
return QRhiTexture::R16;
default: // this cannot assert, must warn and return unknown
qWarning("VkFormat %d is not a recognized uncompressed color format", format);
break;
}
return QRhiTexture::UnknownFormat;
}
static inline bool isDepthTextureFormat(QRhiTexture::Format format)
{
switch (format) {
......@@ -1285,6 +1316,25 @@ QRhi::FrameOpResult QRhiVulkan::endNonWrapperFrame(QRhiSwapChain *swapChain)
QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrame]);
QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImage]);
if (!image.presentableLayout) {
// was used in a readback as transfer source, go back to presentable layout
VkImageMemoryBarrier presTrans;
memset(&presTrans, 0, sizeof(presTrans));
presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
presTrans.image = image.image;
presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
df->vkCmdPipelineBarrier(image.cmdBuf,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, nullptr, 0, nullptr,
1, &presTrans);
image.presentableLayout = true;
}
// stop recording and submit to the queue
Q_ASSERT(!image.cmdFenceWaitable);
QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(image.cmdBuf,
......@@ -1392,8 +1442,24 @@ bool QRhiVulkan::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &
aRb.desc = rb;
aRb.result = result;
QVkTexture *tex = QRHI_RES(QVkTexture, aRb.desc.texture);
textureFormatInfo(tex->m_format, tex->m_pixelSize, nullptr, &aRb.bufSize);
QVkTexture *texD = QRHI_RES(QVkTexture, aRb.desc.texture);
QVkSwapChain *swapChainD = nullptr;
if (texD) {
aRb.pixelSize = texD->m_pixelSize;
aRb.format = texD->m_format;
} else {
Q_ASSERT(currentSwapChain);
swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain);
if (!swapChainD->supportsReadback) {
qWarning("Swapchain does not support readback");
return false;
}
aRb.pixelSize = swapChainD->pixelSize;
aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr);
if (aRb.format == QRhiTexture::UnknownFormat)
return false;
}
textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize);
// Create a host visible buffer.
VkBufferCreateInfo bufferInfo;
......@@ -1423,23 +1489,45 @@ bool QRhiVulkan::readback(QRhiCommandBuffer *cb, const QRhiReadbackDescription &
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.width = aRb.pixelSize.width();
copyDesc.imageExtent.height = aRb.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);
if (texD) {
// assume the image was written
imageBarrier(cb, texD,
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);
df->vkCmdCopyImageToBuffer(cbD->cb, texD->image, texD->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);
// behave as if the image was used for shader read
imageBarrier(cb, texD,
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);
} else {
// use the swapchain image
VkImage image = swapChainD->imageRes[swapChainD->currentImage].image;
VkImageMemoryBarrier barrier;
memset(&barrier, 0, sizeof(barrier));
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = barrier.subresourceRange.layerCount = 1;
barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.image = image;
df->vkCmdPipelineBarrier(cbD->cb,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr,
1, &barrier);
swapChainD->imageRes[swapChainD->currentImage].presentableLayout = false;
df->vkCmdCopyImageToBuffer(cbD->cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, aRb.buf, 1, &copyDesc);
}
activeReadbacks.append(aRb);
return true;
......@@ -2078,6 +2166,8 @@ 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) {
aRb.result->format = aRb.format;
aRb.result->pixelSize = aRb.pixelSize;
aRb.result->data.resize(aRb.bufSize);
void *p = nullptr;
VmaAllocation a = toVmaAllocation(aRb.bufAlloc);
......@@ -2098,6 +2188,7 @@ void QRhiVulkan::finishActiveReadbacks(bool forced)
}
}
// the callback may destroy the result object so invoke it last
for (auto f : completedCallbacks)
f();
}
......
......@@ -280,6 +280,7 @@ struct QVkSwapChain : public QRhiSwapChain
VkFramebuffer fb = VK_NULL_HANDLE;
VkImage msaaImage = VK_NULL_HANDLE;
VkImageView msaaImageView = VK_NULL_HANDLE;
bool presentableLayout = true;
} imageRes[MAX_BUFFER_COUNT];
struct FrameResources {
......@@ -476,6 +477,8 @@ public:
VkBuffer buf;
QVkAlloc bufAlloc;
quint32 bufSize;
QSize pixelSize;
QRhiTexture::Format format;
};
QVector<ActiveReadback> activeReadbacks;
......
mtl, d3d, gl: rhi without a window
mtl, d3d, gl: implement finish()
mtl, d3d, gl: readback
mtl, d3d, gl: readback (tex, backbuffer)
gl, mtl: compressed textures
gl, mtl: srgb (tex, swapchain buf)
multi window? (multi swapchain) -> trouble
......@@ -50,6 +50,7 @@ dxc for d3d as an alternative to fxc?
hlsl -> dxc -> spirv -> spirv-cross hmmm...
+++ done
vk: read back the backbuffer
vk: readback size/formats
vk: readback
vk: implement finish()
......
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