84fb0de413
- The optional nice-to-haves DebugMarkers, Timestamps, PipelineCache are not yet implemented (features reported as false, to be implemented later, although buffer/texture resource name setting already works as-is, regardless of DebugMarkers). - Mipmap generation for 3D textures is missing. Won't matter much given that 3D textures are not used in Qt for anything atm. For generating mipmaps for 2D (or 2D array) textures, the MiniEngine compute shader and approach is used. 3D support for the mipmap generator may be added later. 1D textures / arrays are supported except for mipmap generation, and so the OneDimensionalTextureMipmaps feature is reported as false. - Qt Quick and Qt Quick 3D are expected to be fully functional. (unforeseen issues are not impossible, of course) - Uses minimum feature level 11.0 when requesting the device. It is expected to be functional on resource binding tier 1 hardware even, although this has not been verified in practice. - 2 frames in flight with the usual resource buffering (QRhiBuffer::Dynamic is host visible (UPLOAD) and always mapped and slotted, other buffers and textures are device local (DEFAULT). Requests 3 swapchain buffers. Swapchains are mostly like with D3D11 (e.g. FLIP_DISCARD and SCALING_NONE). - The root signature generation is somewhat limited by the SPIR-V binding model and that we need to map every binding point using the nativeResourceBindingMap from the QShader. Thus the root signature is laid out so each stage has its own set of resources, with shader register clashes being prevented by setting the visibility to a given stage. Sampler handling is somewhat suboptimal but we are tied by the binding model and existing API design. It is in a fairly special situation due to the 2048 limit on a shader visible sampler heap, as opposed to 1000000 for SRVs and UAVS, so the approach we use for textures (just stage the CPU SRVs on the (per-frame slot) shader visible heap as they are encountered, effectively treating the heap as a ring buffer) would quickly lead to having to switch heaps many times with scenes with many draw calls and sampledTexture/sampler bindings in the srb. Whereas static samplers, which would be beautiful, are impossible to utilize safely since we do not have that concept (i.e. samplers specified upfront, tied to the graphics/compute pipeline) in the QRhi API, and an srb used at pipeline creation may change its associated resources, such as the QRhiSampler reference, by the time the shader resources are set for the draw call (or another, compatible srb may get used altogether), so specifying the samplers at root signature creation time is impossible. Rather, the current approach is to treat each sampler as a separate root parameter (per stage) having a descriptor table with a single entry. The shader visible sampler heap has exactly one instance of each unique sampler encountered during the lifetime of the QRhi. - Shader-wise no different from D3D11, works with HLSL/DXBC 5.0 (i.e. existing .qsb files with DXBC in them work as-is). But unlike D3D11, this one will try to pick 6.7, 6.6, ..., down to 5.0 from the QShader, in that order. - Uses D3D12MA for suballocating. As a result it can report vmem allocation statistics like the Vulkan backend, and it does more since the DXGI memory usage (incl. implicit resources) is also reported. This is optional technically, so we also have the option of going straight with the heavyweight CreateCommittedResource() instead. That is what we do if the adapter chosen reports it's software-based or when QT_D3D_NO_SUBALLOC=1 is set. - PreferSoftwareRenderer (picking the WARP device) and the env.var. QT_D3D_ADAPTER_INDEX work as with the D3D11 backend. - It is not unexpected that with large scenes that generate lots of draw calls with multiple textures/samplers per call the performance may be slightly below D3D11 (probably mostly due to descriptor management). Similarly, the reported memory usage will be higher, which is partly natural due to creating heaps, descriptor pools, staging areas, etc. upfront. Will need to be evaluated later how these can be tuned. Change-Id: I5a42580bb65f391ebceaf81adc6ae673cceacb74 Reviewed-by: Andy Nichols <andy.nichols@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
320 lines
9.5 KiB
C++
320 lines
9.5 KiB
C++
// Copyright (C) 2018 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
#include <QGuiApplication>
|
|
#include <QImage>
|
|
#include <QFileInfo>
|
|
#include <QFile>
|
|
#include <QLoggingCategory>
|
|
#include <QCommandLineParser>
|
|
#include <QtGui/private/qshader_p.h>
|
|
|
|
#include <QtGui/private/qrhinull_p.h>
|
|
|
|
#ifndef QT_NO_OPENGL
|
|
#include <QtGui/private/qrhigles2_p.h>
|
|
#include <QOffscreenSurface>
|
|
#endif
|
|
|
|
#if QT_CONFIG(vulkan)
|
|
#include <QLoggingCategory>
|
|
#include <QtGui/private/qrhivulkan_p.h>
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <QtGui/private/qrhid3d11_p.h>
|
|
#include <QtGui/private/qrhid3d12_p.h>
|
|
#endif
|
|
|
|
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
|
|
#include <QtGui/private/qrhimetal_p.h>
|
|
#endif
|
|
|
|
//#define TEST_FINISH
|
|
|
|
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 QShader getShader(const QString &name)
|
|
{
|
|
QFile f(name);
|
|
if (f.open(QIODevice::ReadOnly))
|
|
return QShader::fromSerialized(f.readAll());
|
|
|
|
return QShader();
|
|
}
|
|
|
|
enum GraphicsApi
|
|
{
|
|
OpenGL,
|
|
Vulkan,
|
|
D3D11,
|
|
D3D12,
|
|
Metal,
|
|
Null
|
|
};
|
|
|
|
GraphicsApi graphicsApi;
|
|
|
|
QString graphicsApiName()
|
|
{
|
|
switch (graphicsApi) {
|
|
case OpenGL:
|
|
return QLatin1String("OpenGL 2.x");
|
|
case Vulkan:
|
|
return QLatin1String("Vulkan");
|
|
case D3D11:
|
|
return QLatin1String("Direct3D 11");
|
|
case D3D12:
|
|
return QLatin1String("Direct3D 12");
|
|
case Metal:
|
|
return QLatin1String("Metal");
|
|
case Null:
|
|
return QLatin1String("Null");
|
|
default:
|
|
break;
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
QGuiApplication app(argc, argv);
|
|
|
|
#if defined(Q_OS_WIN)
|
|
graphicsApi = D3D11;
|
|
#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
|
|
graphicsApi = Metal;
|
|
#elif QT_CONFIG(vulkan)
|
|
graphicsApi = Vulkan;
|
|
#else
|
|
graphicsApi = OpenGL;
|
|
#endif
|
|
|
|
QCommandLineParser cmdLineParser;
|
|
cmdLineParser.addHelpOption();
|
|
QCommandLineOption glOption({ "g", "opengl" }, QLatin1String("OpenGL (2.x)"));
|
|
cmdLineParser.addOption(glOption);
|
|
QCommandLineOption vkOption({ "v", "vulkan" }, QLatin1String("Vulkan"));
|
|
cmdLineParser.addOption(vkOption);
|
|
QCommandLineOption d3d11Option({ "d", "d3d11" }, QLatin1String("Direct3D 11"));
|
|
cmdLineParser.addOption(d3d11Option);
|
|
QCommandLineOption d3d12Option({ "D", "d3d12" }, QLatin1String("Direct3D 12"));
|
|
cmdLineParser.addOption(d3d12Option);
|
|
QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal"));
|
|
cmdLineParser.addOption(mtlOption);
|
|
QCommandLineOption nullOption({ "n", "null" }, QLatin1String("Null"));
|
|
cmdLineParser.addOption(nullOption);
|
|
cmdLineParser.process(app);
|
|
if (cmdLineParser.isSet(glOption))
|
|
graphicsApi = OpenGL;
|
|
if (cmdLineParser.isSet(vkOption))
|
|
graphicsApi = Vulkan;
|
|
if (cmdLineParser.isSet(d3d11Option))
|
|
graphicsApi = D3D11;
|
|
if (cmdLineParser.isSet(d3d12Option))
|
|
graphicsApi = D3D12;
|
|
if (cmdLineParser.isSet(mtlOption))
|
|
graphicsApi = Metal;
|
|
if (cmdLineParser.isSet(nullOption))
|
|
graphicsApi = Null;
|
|
|
|
qDebug("Selected graphics API is %s", qPrintable(graphicsApiName()));
|
|
qDebug("This is a multi-api example, use command line arguments to override:\n%s", qPrintable(cmdLineParser.helpText()));
|
|
|
|
QRhi *r = nullptr;
|
|
|
|
if (graphicsApi == Null) {
|
|
QRhiNullInitParams params;
|
|
r = QRhi::create(QRhi::Null, ¶ms);
|
|
}
|
|
|
|
#if QT_CONFIG(vulkan)
|
|
QVulkanInstance inst;
|
|
if (graphicsApi == Vulkan) {
|
|
QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true"));
|
|
inst.setLayers({ "VK_LAYER_KHRONOS_validation" });
|
|
if (inst.create()) {
|
|
QRhiVulkanInitParams params;
|
|
params.inst = &inst;
|
|
r = QRhi::create(QRhi::Vulkan, ¶ms);
|
|
} else {
|
|
qWarning("Failed to create Vulkan instance, switching to OpenGL");
|
|
graphicsApi = OpenGL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifndef QT_NO_OPENGL
|
|
QScopedPointer<QOffscreenSurface> offscreenSurface;
|
|
if (graphicsApi == OpenGL) {
|
|
offscreenSurface.reset(QRhiGles2InitParams::newFallbackSurface());
|
|
QRhiGles2InitParams params;
|
|
params.fallbackSurface = offscreenSurface.data();
|
|
r = QRhi::create(QRhi::OpenGLES2, ¶ms);
|
|
}
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
if (graphicsApi == D3D11) {
|
|
QRhiD3D11InitParams params;
|
|
params.enableDebugLayer = true;
|
|
r = QRhi::create(QRhi::D3D11, ¶ms);
|
|
} else if (graphicsApi == D3D12) {
|
|
QRhiD3D12InitParams params;
|
|
params.enableDebugLayer = true;
|
|
r = QRhi::create(QRhi::D3D12, ¶ms);
|
|
}
|
|
#endif
|
|
|
|
#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
|
|
if (graphicsApi == Metal) {
|
|
QRhiMetalInitParams params;
|
|
r = QRhi::create(QRhi::Metal, ¶ms);
|
|
}
|
|
#endif
|
|
|
|
if (!r)
|
|
qFatal("Failed to initialize RHI");
|
|
|
|
QRhiTexture *tex = r->newTexture(QRhiTexture::RGBA8, QSize(1280, 720), 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
|
|
tex->create();
|
|
QRhiTextureRenderTarget *rt = r->newTextureRenderTarget({ tex });
|
|
QRhiRenderPassDescriptor *rp = rt->newCompatibleRenderPassDescriptor();
|
|
rt->setRenderPassDescriptor(rp);
|
|
rt->create();
|
|
|
|
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->create();
|
|
|
|
QRhiBuffer *ubuf = r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
|
|
ubuf->create();
|
|
|
|
QRhiShaderResourceBindings *srb = r->newShaderResourceBindings();
|
|
srb->setBindings({
|
|
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf)
|
|
});
|
|
srb->create();
|
|
|
|
QRhiGraphicsPipeline *ps = r->newGraphicsPipeline();
|
|
|
|
QRhiGraphicsPipeline::TargetBlend premulAlphaBlend;
|
|
premulAlphaBlend.enable = true;
|
|
ps->setTargetBlends({ premulAlphaBlend });
|
|
|
|
const QShader vs = getShader(QLatin1String(":/color.vert.qsb"));
|
|
if (!vs.isValid())
|
|
qFatal("Failed to load shader pack (vertex)");
|
|
const QShader fs = getShader(QLatin1String(":/color.frag.qsb"));
|
|
if (!fs.isValid())
|
|
qFatal("Failed to load shader pack (fragment)");
|
|
|
|
ps->setShaderStages({
|
|
{ QRhiShaderStage::Vertex, vs },
|
|
{ QRhiShaderStage::Fragment, fs }
|
|
});
|
|
|
|
QRhiVertexInputLayout inputLayout;
|
|
inputLayout.setBindings({
|
|
{ 5 * sizeof(float) }
|
|
});
|
|
inputLayout.setAttributes({
|
|
{ 0, 0, QRhiVertexInputAttribute::Float2, 0 },
|
|
{ 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
|
|
});
|
|
|
|
ps->setVertexInputLayout(inputLayout);
|
|
ps->setShaderResourceBindings(srb);
|
|
ps->setRenderPassDescriptor(rp);
|
|
ps->create();
|
|
|
|
int frame = 0;
|
|
for (; frame < 20; ++frame) {
|
|
QRhiCommandBuffer *cb;
|
|
if (r->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
|
|
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, QColor::fromRgbF(0.0f, 1.0f, 0.0f, 1.0f), { 1, 0 }, u);
|
|
cb->setGraphicsPipeline(ps);
|
|
cb->setViewport({ 0, 0, 1280, 720 });
|
|
cb->setShaderResources();
|
|
const QRhiCommandBuffer::VertexInput vbufBinding(vbuf, 0);
|
|
cb->setVertexInput(0, 1, &vbufBinding);
|
|
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");
|
|
#ifdef TEST_FINISH
|
|
r->finish();
|
|
#else
|
|
r->endOffscreenFrame();
|
|
#endif
|
|
// The data should be ready either because endOffscreenFrame() waits
|
|
// for completion or because finish() did.
|
|
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));
|
|
if (r->isYUpInFramebuffer())
|
|
image.mirrored().save(fn);
|
|
else
|
|
image.save(fn);
|
|
} else {
|
|
qWarning("Readback failed!");
|
|
}
|
|
#ifdef TEST_FINISH
|
|
r->endOffscreenFrame();
|
|
#endif
|
|
}
|
|
|
|
delete ps;
|
|
delete srb;
|
|
delete ubuf;
|
|
delete vbuf;
|
|
|
|
delete rt;
|
|
delete rp;
|
|
delete tex;
|
|
|
|
delete r;
|
|
|
|
qDebug("\nRendered and read back %d frames using %s", frame, qPrintable(graphicsApiName()));
|
|
|
|
return 0;
|
|
}
|