Commit b4cfad8c authored by Laszlo Agocs's avatar Laszlo Agocs

mtl: Add support for readbacks and offscreen frames

parent 731c3705
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QGuiApplication>
#include <QImage>
#include <QFileInfo>
#include <QFile>
#include <QBakedShader>
#include <QRhiMetalInitParams>
static float vertexData[] = { // Y up (note m_proj), CCW
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
};
static QBakedShader getShader(const QString &name)
{
QFile f(name);
if (f.open(QIODevice::ReadOnly))
return QBakedShader::fromSerialized(f.readAll());
return QBakedShader();
}
int main(int argc, char **argv)
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QRhiMetalInitParams params;
QRhi *r = QRhi::create(QRhi::Metal, &params);
if (!r) {
qWarning("Failed to initialize RHI");
return 1;
}
QRhiTexture *tex = r->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
tex->build();
QRhiTextureRenderTarget *rt = r->newTextureRenderTarget({ tex });
QRhiRenderPassDescriptor *rp = rt->newCompatibleRenderPassDescriptor();
rt->setRenderPassDescriptor(rp);
rt->build();
QMatrix4x4 proj = r->clipSpaceCorrMatrix();
proj.perspective(45.0f, 1280 / 720.f, 0.01f, 1000.0f);
proj.translate(0, 0, -4);
QRhiBuffer *vbuf = r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
vbuf->build();
QRhiBuffer *ubuf = r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
ubuf->build();
QRhiShaderResourceBindings *srb = r->newShaderResourceBindings();
srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf)
});
srb->build();
QRhiGraphicsPipeline *ps = r->newGraphicsPipeline();
QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
premulAlphaBlend.enable = true;
ps->setTargetBlends({ premulAlphaBlend });
const QBakedShader vs = getShader(QLatin1String(":/color.vert.qsb"));
if (!vs.isValid())
qFatal("Failed to load shader pack (vertex)");
const QBakedShader fs = getShader(QLatin1String(":/color.frag.qsb"));
if (!fs.isValid())
qFatal("Failed to load shader pack (fragment)");
ps->setShaderStages({
{ QRhiGraphicsShaderStage::Vertex, vs },
{ QRhiGraphicsShaderStage::Fragment, fs }
});
QRhiVertexInputLayout inputLayout;
inputLayout.bindings = {
{ 5 * sizeof(float) }
};
inputLayout.attributes = {
{ 0, 0, QRhiVertexInputLayout::Attribute::Float2, 0 },
{ 0, 1, QRhiVertexInputLayout::Attribute::Float3, 2 * sizeof(float) }
};
ps->setVertexInputLayout(inputLayout);
ps->setShaderResourceBindings(srb);
ps->setRenderPassDescriptor(rp);
ps->build();
for (int frame = 0; frame < 20; ++frame) {
QRhiCommandBuffer *cb;
if (r->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess) {
qDebug("Failed to start frame");
break;
}
qDebug("Generating offscreen frame %d", frame);
QRhiResourceUpdateBatch *u = r->nextResourceUpdateBatch();
if (frame == 0)
u->uploadStaticBuffer(vbuf, vertexData);
static float rotation = 0.0f;
QMatrix4x4 mvp = proj;
mvp.rotate(rotation, 0, 1, 0);
u->updateDynamicBuffer(ubuf, 0, 64, mvp.constData());
rotation += 5.0f;
static float opacity = 1.0f;
static int opacityDir= 1;
u->updateDynamicBuffer(ubuf, 64, 4, &opacity);
opacity += opacityDir * 0.005f;
if (opacity < 0.0f || opacity > 1.0f) {
opacityDir *= -1;
opacity = qBound(0.0f, opacity, 1.0f);
}
cb->beginPass(rt, { 0, 1, 0, 1 }, { 1, 0 }, u);
cb->setGraphicsPipeline(ps);
cb->setViewport({ 0, 0, 1280, 720 });
cb->setVertexInput(0, { { vbuf, 0 } });
cb->draw(3);
u = r->nextResourceUpdateBatch();
QRhiReadbackDescription rb(tex);
QRhiReadbackResult rbResult;
rbResult.completed = [frame] { qDebug(" - readback %d completed", frame); };
u->readBackTexture(rb, &rbResult);
cb->endPass(u);
qDebug("Submit and wait");
r->endOffscreenFrame();
// No finish() or waiting for the completed callback is needed here
// 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, 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));
image.save(fn);
} else {
qWarning("Readback failed!");
}
}
ps->releaseAndDestroy();
srb->releaseAndDestroy();
ubuf->releaseAndDestroy();
vbuf->releaseAndDestroy();
rt->releaseAndDestroy();
rp->releaseAndDestroy();
tex->releaseAndDestroy();
delete r;
return 0;
}
TEMPLATE = app
CONFIG += console
QT += shadertools rhi
SOURCES = \
main.cpp
RESOURCES = offscreen_metal.qrc
target.path = $$[QT_INSTALL_EXAMPLES]/rhi/offscreen_metal
INSTALLS += target
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file alias="color.vert.qsb">../shared/color.vert.qsb</file>
<file alias="color.frag.qsb">../shared/color.frag.qsb</file>
</qresource>
</RCC>
......@@ -25,5 +25,6 @@ win32 {
mac {
SUBDIRS += \
plainqwindow_metal
plainqwindow_metal \
offscreen_metal
}
......@@ -114,9 +114,14 @@ void ExampleWindow::init()
m_sc->setWindow(this);
m_sc->setDepthStencil(m_ds);
m_sc->setSampleCount(m_sampleCount);
QRhiSwapChain::Flags scFlags = 0;
#ifdef READBACK_SWAPCHAIN
scFlags |= QRhiSwapChain::UsedAsTransferSource;
#endif
#ifdef USE_SRGB_SWAPCHAIN
m_sc->setFlags(QRhiSwapChain::sRGB);
scFlags |= QRhiSwapChain::sRGB;
#endif
m_sc->setFlags(scFlags);
m_scrp = m_sc->newCompatibleRenderPassDescriptor();
m_sc->setRenderPassDescriptor(m_scrp);
......
......@@ -835,18 +835,19 @@ Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_MOVABLE_TYPE);
class Q_RHI_EXPORT QRhiSwapChain : public QRhiResource
{
public:
enum SurfaceImportFlag {
enum Flag {
SurfaceHasPreMulAlpha = 1 << 0,
SurfaceHasNonPreMulAlpha = 1 << 1,
sRGB = 1 << 2
sRGB = 1 << 2,
UsedAsTransferSource = 1 << 3 // will be read back
};
Q_DECLARE_FLAGS(SurfaceImportFlags, SurfaceImportFlag)
Q_DECLARE_FLAGS(Flags, Flag)
QWindow *window() const { return m_window; }
void setWindow(QWindow *window) { m_window = window; }
SurfaceImportFlags flags() const { return m_flags; }
void setFlags(SurfaceImportFlags f) { m_flags = f; }
Flags flags() const { return m_flags; }
void setFlags(Flags f) { m_flags = f; }
QRhiRenderBuffer *depthStencil() const { return m_depthStencil; }
void setDepthStencil(QRhiRenderBuffer *ds) { m_depthStencil = ds; }
......@@ -896,7 +897,7 @@ public:
protected:
QRhiSwapChain(QRhiImplementation *rhi);
QWindow *m_window = nullptr;
SurfaceImportFlags m_flags;
Flags m_flags;
QRhiRenderBuffer *m_depthStencil = nullptr;
int m_sampleCount = 1;
QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
......@@ -905,7 +906,7 @@ protected:
void *m_reserved;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::SurfaceImportFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags)
class Q_RHI_EXPORT QRhiCommandBuffer : public QRhiResource
{
......@@ -1159,8 +1160,10 @@ public:
is to use it in completely offscreen applications, e.g. to generate image
sequences by rendering and reading back without ever showing a window.
Usage in on-screen applications (so beginFrame, endFrame,
beginOffscreenFrame, endOffscreenFrame, beginFrame, ...) is possible too but
it does break parallelism so should be done only infrequently.
beginOffscreenFrame, endOffscreenFrame, beginFrame, ...) is possible too
but it does reduce parallelism (offscreen frames do not let the CPU -
potentially - generate another frame while the GPU is still processing
the previous one) so should be done only infrequently.
QRhiReadbackResult rbResult;
QRhiCommandBuffer *cb; // not owned
beginOffscreenFrame(&cb);
......
......@@ -65,6 +65,8 @@ QT_BEGIN_NAMESPACE
struct QRhiMetalData
{
QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
id<MTLDevice> dev;
id<MTLCommandQueue> cmdQueue;
......@@ -104,8 +106,28 @@ struct QRhiMetalData
};
};
QVector<DeferredReleaseEntry> releaseQueue;
struct OffscreenFrame {
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
QMetalCommandBuffer cbWrapper;
} ofr;
struct ActiveReadback {
int activeFrameSlot = -1;
QRhiReadbackDescription desc;
QRhiReadbackResult *result;
id<MTLBuffer> buf;
quint32 bufSize;
QSize pixelSize;
QRhiTexture::Format format;
};
QVector<ActiveReadback> activeReadbacks;
};
Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
struct QMetalBufferData
{
bool managed;
......@@ -143,15 +165,17 @@ struct QMetalRenderTargetData
QSize pixelSize;
int colorAttCount = 0;
int dsAttCount = 0;
struct ColorAtt {
id<MTLTexture> tex = nil;
int layer = 0;
int level = 0;
id<MTLTexture> resolveTex = nil;
int resolveLayer = 0;
int resolveLevel = 0;
};
struct {
struct ColorAtt {
id<MTLTexture> tex = nil;
int layer = 0;
int level = 0;
id<MTLTexture> resolveTex = nil;
int resolveLayer = 0;
int resolveLevel = 0;
};
ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
id<MTLTexture> dsTex = nil;
bool hasStencil = false;
......@@ -179,12 +203,13 @@ struct QMetalSwapChainData
id<MTLCommandBuffer> cb[QMTL_FRAMES_IN_FLIGHT];
MTLRenderPassDescriptor *rp = nullptr;
id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
QRhiTexture::Format rhiColorFormat;
MTLPixelFormat colorFormat;
};
QRhiMetal::QRhiMetal(QRhiInitParams *params)
{
d = new QRhiMetalData;
d = new QRhiMetalData(this);
QRhiMetalInitParams *metalparams = static_cast<QRhiMetalInitParams *>(params);
importedDevice = metalparams->importExistingDevice;
......@@ -220,6 +245,7 @@ void QRhiMetal::create()
void QRhiMetal::destroy()
{
executeDeferredReleases(true);
finishActiveReadbacks(true);
if (d->cmdQueue) {
[d->cmdQueue release];
......@@ -558,6 +584,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain)
return QRhi::FrameOpSwapChainOutOfDate;
}
currentSwapChain = swapChainD;
currentFrameSlot = swapChainD->currentFrame;
if (swapChainD->ds)
swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
......@@ -568,7 +595,6 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain)
swapChainD->d->cb[currentFrameSlot] = [d->cmdQueue commandBufferWithUnretainedReferences];
swapChainD->cbWrapper.d->cb = swapChainD->d->cb[currentFrameSlot];
swapChainD->cbWrapper.resetState();
id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
id<MTLTexture> resolveTex = nil;
......@@ -582,6 +608,8 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain)
swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
executeDeferredReleases();
swapChainD->cbWrapper.resetState();
finishActiveReadbacks();
return QRhi::FrameOpSuccess;
}
......@@ -603,6 +631,7 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain)
[swapChainD->d->cb[currentFrameSlot] commit];
swapChainD->currentFrame = (swapChainD->currentFrame + 1) % QMTL_FRAMES_IN_FLIGHT;
currentSwapChain = nullptr;
++finishedFrameCount;
return QRhi::FrameOpSuccess;
......@@ -610,13 +639,45 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain)
QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
{
Q_UNUSED(cb);
return QRhi::FrameOpError;
Q_ASSERT(!inFrame);
inFrame = true;
// Switch to the next slot manually. Swapchains do not know about this
// which is good. So for example a - unusual but possible - onscreen,
// onscreen, offscreen, onscreen, onscreen, onscreen sequence of
// begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the
// offscreen frame is synchronous in the sense that we wait for execution
// to complete in endFrame, and so no resources used in that frame are busy
// anymore in the next frame.
currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
d->ofr.active = true;
*cb = &d->ofr.cbWrapper;
d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
executeDeferredReleases();
d->ofr.cbWrapper.resetState();
finishActiveReadbacks();
return QRhi::FrameOpSuccess;
}
QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
{
return QRhi::FrameOpError;
Q_ASSERT(d->ofr.active);
d->ofr.active = false;
Q_ASSERT(inFrame);
inFrame = false;
[d->ofr.cbWrapper.d->cb commit];
// offscreen frames wait for completion, unlike swapchain ones
[d->ofr.cbWrapper.d->cb waitUntilCompleted];
finishActiveReadbacks(true);
++finishedFrameCount;
return QRhi::FrameOpSuccess;
}
QRhi::FrameOpResult QRhiMetal::finish()
......@@ -827,6 +888,60 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
destinationOrigin: MTLOriginMake(dx, dy, 0)];
}
for (const QRhiResourceUpdateBatchPrivate::TextureRead &u : ud->textureReadbacks) {
QRhiMetalData::ActiveReadback aRb;
aRb.activeFrameSlot = currentFrameSlot;
aRb.desc = u.rb;
aRb.result = u.result;
QMetalTexture *texD = QRHI_RES(QMetalTexture, aRb.desc.texture);
QMetalSwapChain *swapChainD = nullptr;
id<MTLTexture> src;
QSize srcSize;
if (texD) {
if (texD->samples > 1) {
qWarning("Multisample texture cannot be read back");
continue;
}
aRb.pixelSize = texD->m_pixelSize;
if (u.rb.level > 0) {
aRb.pixelSize.setWidth(qFloor(float(qMax(1, aRb.pixelSize.width() >> u.rb.level))));
aRb.pixelSize.setHeight(qFloor(float(qMax(1, aRb.pixelSize.height() >> u.rb.level))));
}
aRb.format = texD->m_format;
src = texD->d->tex;
srcSize = texD->m_pixelSize;
} else {
Q_ASSERT(currentSwapChain);
swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
aRb.pixelSize = swapChainD->pixelSize;
aRb.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]);
src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
srcSize = swapChainD->rtWrapper.d->pixelSize;
}
quint32 bpl = 0;
textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
ensureBlit();
[blitEnc copyFromTexture: src
sourceSlice: aRb.desc.layer
sourceLevel: aRb.desc.level
sourceOrigin: MTLOriginMake(0, 0, 0)
sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
toBuffer: aRb.buf
destinationOffset: 0
destinationBytesPerRow: bpl
destinationBytesPerImage: 0
options: MTLBlitOptionNone];
d->activeReadbacks.append(aRb);
}
for (const QRhiResourceUpdateBatchPrivate::TextureMipGen &u : ud->textureMipGens) {
ensureBlit();
[blitEnc generateMipmapsForTexture: QRHI_RES(QMetalTexture, u.tex)->d->tex];
......@@ -974,6 +1089,31 @@ void QRhiMetal::executeDeferredReleases(bool forced)
}
}
void QRhiMetal::finishActiveReadbacks(bool forced)
{
QVarLengthArray<std::function<void()>, 4> completedCallbacks;
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(aRb.bufSize);
void *p = [aRb.buf contents];
memcpy(aRb.result->data.data(), p, aRb.bufSize);
[aRb.buf release];
if (aRb.result->completed)
completedCallbacks.append(aRb.result->completed);
d->activeReadbacks.removeAt(i);
}
}
for (auto f : completedCallbacks)
f();
}
QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
: QRhiBuffer(rhi, type, usage, size),
d(new QMetalBufferData)
......@@ -2116,6 +2256,7 @@ void QMetalSwapChain::chooseFormats()
samples = rhiD->effectiveSampleCount(m_sampleCount);
// pick a format that is allowed for CAMetalLayer.pixelFormat
d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
d->rhiColorFormat = QRhiTexture::BGRA8;
}
bool QMetalSwapChain::buildOrResize()
......@@ -2136,6 +2277,9 @@ bool QMetalSwapChain::buildOrResize()
if (d->colorFormat != d->layer.pixelFormat)
d->layer.pixelFormat = d->colorFormat;
if (m_flags.testFlag(UsedAsTransferSource))
d->layer.framebufferOnly = NO;
m_currentPixelSize = surfacePixelSize();
pixelSize = m_currentPixelSize;
......
......@@ -315,6 +315,7 @@ public:
void create();
void destroy();
void executeDeferredReleases(bool forced = false);
void finishActiveReadbacks(bool forced = false);
void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD);
int effectiveSampleCount(int sampleCount) const;
......@@ -324,6 +325,7 @@ public:
int currentFrameSlot = 0;
int finishedFrameCount = 0;
bool inPass = false;
QMetalSwapChain *currentSwapChain = nullptr;
QRhiMetalData *d = nullptr;
};
......
......@@ -940,7 +940,7 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
if (swapChainD->supportsReadback)
if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
qDebug("Creating new swapchain of %d buffers, size %dx%d",
......
mtl: rhi without a window, offscreen frame
mtl: readback (tex, backbuffer)
mtl: finish()
gl: tex formats (texture, readback)
gl: srgb
......@@ -55,6 +53,8 @@ dxc for d3d as an alternative to fxc?
hlsl -> dxc -> spirv -> spirv-cross hmmm...
+++ done
mtl: readback (tex, backbuffer)
mtl: rhi without a window, offscreen frame
mtl: compressed textures
mtl: srgb (tex, swapchain buf)
change how resolve is done
......
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