diff options
Diffstat (limited to 'tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp')
-rw-r--r-- | tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp new file mode 100644 index 0000000000..8fda2b73c8 --- /dev/null +++ b/tests/manual/rhi/multiwindow_threaded/multiwindow_threaded.cpp @@ -0,0 +1,829 @@ +/**************************************************************************** +** +** 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 <QApplication> +#include <QWidget> +#include <QLabel> +#include <QPlainTextEdit> +#include <QPushButton> +#include <QCheckBox> +#include <QVBoxLayout> + +#include <QThread> +#include <QMutex> +#include <QWaitCondition> +#include <QQueue> +#include <QEvent> +#include <QCommandLineParser> +#include <QElapsedTimer> + +#include <QtGui/private/qshader_p.h> +#include <QFile> +#include <QtGui/private/qrhiprofiler_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> +#endif + +#ifdef Q_OS_DARWIN +#include <QtGui/private/qrhimetal_p.h> +#endif + +#include "window.h" + +#include "../shared/cube.h" + +static QShader getShader(const QString &name) +{ + QFile f(name); + if (f.open(QIODevice::ReadOnly)) + return QShader::fromSerialized(f.readAll()); + + return QShader(); +} + +static GraphicsApi graphicsApi; + +static QString graphicsApiName() +{ + switch (graphicsApi) { + case OpenGL: + return QLatin1String("OpenGL 2.x"); + case Vulkan: + return QLatin1String("Vulkan"); + case D3D11: + return QLatin1String("Direct3D 11"); + case Metal: + return QLatin1String("Metal"); + default: + break; + } + return QString(); +} + +#if QT_CONFIG(vulkan) +QVulkanInstance *instance = nullptr; +#endif + +// Window (main thread) emit signals -> Renderer::send* (main thread) -> event queue (add on main, process on render thread) -> Renderer::renderEvent (render thread) + +// event queue is taken from the Qt Quick scenegraph as-is +// all this below is conceptually the same as the QSG threaded render loop +class RenderThreadEventQueue : public QQueue<QEvent *> +{ +public: + RenderThreadEventQueue() + : waiting(false) + { + } + + void addEvent(QEvent *e) { + mutex.lock(); + enqueue(e); + if (waiting) + condition.wakeOne(); + mutex.unlock(); + } + + QEvent *takeEvent(bool wait) { + mutex.lock(); + if (isEmpty() && wait) { + waiting = true; + condition.wait(&mutex); + waiting = false; + } + QEvent *e = dequeue(); + mutex.unlock(); + return e; + } + + bool hasMoreEvents() { + mutex.lock(); + bool has = !isEmpty(); + mutex.unlock(); + return has; + } + +private: + QMutex mutex; + QWaitCondition condition; + bool waiting; +}; + +struct Renderer; + +struct Thread : public QThread +{ + Thread(Renderer *renderer_) + : renderer(renderer_) + { + active = true; + start(); + } + void run() override; + + Renderer *renderer; + bool active; + RenderThreadEventQueue eventQueue; + bool sleeping = false; + bool stopEventProcessing = false; + bool pendingRender = false; + bool pendingRenderIsNewExpose = false; + // mutex and cond used to allow the main thread waiting until something completes on the render thread + QMutex mutex; + QWaitCondition cond; +}; + +class RenderThreadEvent : public QEvent +{ +public: + RenderThreadEvent(QEvent::Type type) : QEvent(type) { } +}; + +class InitEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 1); + InitEvent() : RenderThreadEvent(TYPE) + { } +}; + +class RequestRenderEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 2); + RequestRenderEvent(bool newlyExposed_) : RenderThreadEvent(TYPE), newlyExposed(newlyExposed_) + { } + bool newlyExposed; +}; + +class SurfaceCleanupEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 3); + SurfaceCleanupEvent() : RenderThreadEvent(TYPE) + { } +}; + +class CloseEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 4); + CloseEvent() : RenderThreadEvent(TYPE) + { } +}; + +class SyncSurfaceSizeEvent : public RenderThreadEvent +{ +public: + static const QEvent::Type TYPE = QEvent::Type(QEvent::User + 5); + SyncSurfaceSizeEvent() : RenderThreadEvent(TYPE) + { } +}; + +struct Renderer +{ + // ctor and dtor and send* are called main thread, rest on the render thread + + Renderer(QWindow *w, const QColor &bgColor, int rotationAxis); + ~Renderer(); + + void sendInit(); + void sendRender(bool newlyExposed); + void sendSurfaceGoingAway(); + void sendSyncSurfaceSize(); + + QWindow *window; + Thread *thread; + QRhi *r = nullptr; +#ifndef QT_NO_OPENGL + QOffscreenSurface *fallbackSurface = nullptr; +#endif + + void createRhi(); + void destroyRhi(); + void renderEvent(QEvent *e); + void init(); + void releaseSwapChain(); + void releaseResources(); + void render(bool newlyExposed, bool wakeBeforePresent); + + QColor m_bgColor; + int m_rotationAxis; + + QVector<QRhiResource *> m_releasePool; + bool m_hasSwapChain = false; + QRhiSwapChain *m_sc = nullptr; + QRhiRenderBuffer *m_ds = nullptr; + QRhiRenderPassDescriptor *m_rp = nullptr; + + QRhiBuffer *m_vbuf = nullptr; + QRhiBuffer *m_ubuf = nullptr; + QRhiTexture *m_tex = nullptr; + QRhiSampler *m_sampler = nullptr; + QRhiShaderResourceBindings *m_srb = nullptr; + QRhiGraphicsPipeline *m_ps = nullptr; + QRhiResourceUpdateBatch *m_initialUpdates = nullptr; + + QMatrix4x4 m_proj; + float m_rotation = 0; + int m_frameCount = 0; +}; + +void Thread::run() +{ + while (active) { + if (pendingRender) { + pendingRender = false; + renderer->render(pendingRenderIsNewExpose, false); + } + + while (eventQueue.hasMoreEvents()) { + QEvent *e = eventQueue.takeEvent(false); + renderer->renderEvent(e); + delete e; + } + + if (active && !pendingRender) { + sleeping = true; + stopEventProcessing = false; + while (!stopEventProcessing) { + QEvent *e = eventQueue.takeEvent(true); + renderer->renderEvent(e); + delete e; + } + sleeping = false; + } + } +} + +Renderer::Renderer(QWindow *w, const QColor &bgColor, int rotationAxis) + : window(w), + m_bgColor(bgColor), + m_rotationAxis(rotationAxis) +{ // main thread + thread = new Thread(this); + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) + fallbackSurface = QRhiGles2InitParams::newFallbackSurface(); +#endif +} + +Renderer::~Renderer() +{ // main thread + thread->eventQueue.addEvent(new CloseEvent); + thread->wait(); + delete thread; + +#ifndef QT_NO_OPENGL + delete fallbackSurface; +#endif +} + +void Renderer::createRhi() +{ + if (r) + return; + + qDebug() << "renderer" << this << "creating rhi"; + QRhi::Flags rhiFlags = QRhi::EnableProfiling; + +#ifndef QT_NO_OPENGL + if (graphicsApi == OpenGL) { + QRhiGles2InitParams params; + params.fallbackSurface = fallbackSurface; + params.window = window; + r = QRhi::create(QRhi::OpenGLES2, ¶ms, rhiFlags); + } +#endif + +#if QT_CONFIG(vulkan) + if (graphicsApi == Vulkan) { + QRhiVulkanInitParams params; + params.inst = instance; + params.window = window; + r = QRhi::create(QRhi::Vulkan, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_WIN + if (graphicsApi == D3D11) { + QRhiD3D11InitParams params; + params.enableDebugLayer = true; + r = QRhi::create(QRhi::D3D11, ¶ms, rhiFlags); + } +#endif + +#ifdef Q_OS_DARWIN + if (graphicsApi == Metal) { + QRhiMetalInitParams params; + r = QRhi::create(QRhi::Metal, ¶ms, rhiFlags); + } +#endif + + if (!r) + qFatal("Failed to create RHI backend"); +} + +void Renderer::destroyRhi() +{ + qDebug() << "renderer" << this << "destroying rhi"; + delete r; + r = nullptr; +} + +void Renderer::renderEvent(QEvent *e) +{ + Q_ASSERT(QThread::currentThread() == thread); + + if (thread->sleeping) + thread->stopEventProcessing = true; + + switch (int(e->type())) { + case InitEvent::TYPE: + qDebug() << "renderer" << this << "for window" << window << "is initializing"; + createRhi(); + init(); + break; + case RequestRenderEvent::TYPE: + thread->pendingRender = true; + thread->pendingRenderIsNewExpose = static_cast<RequestRenderEvent *>(e)->newlyExposed; + break; + case SurfaceCleanupEvent::TYPE: // when the QWindow is closed, before QPlatformWindow goes away + thread->mutex.lock(); + qDebug() << "renderer" << this << "for window" << window << "is destroying swapchain"; + thread->pendingRender = false; + releaseSwapChain(); + thread->cond.wakeOne(); + thread->mutex.unlock(); + break; + case CloseEvent::TYPE: // when destroying the window+renderer (NB not the same as hitting X on the window, that's just QWindow close) + qDebug() << "renderer" << this << "for window" << window << "is shutting down"; + thread->pendingRender = false; + thread->active = false; + thread->stopEventProcessing = true; + releaseResources(); + destroyRhi(); + break; + case SyncSurfaceSizeEvent::TYPE: + thread->mutex.lock(); + thread->pendingRender = false; + render(false, true); + break; + default: + break; + } +} + +void Renderer::init() +{ + m_sc = r->newSwapChain(); + m_ds = r->newRenderBuffer(QRhiRenderBuffer::DepthStencil, + QSize(), // no need to set the size yet + 1, + QRhiRenderBuffer::UsedWithSwapChainOnly); + m_releasePool << m_ds; + m_sc->setWindow(window); + m_sc->setDepthStencil(m_ds); + m_rp = m_sc->newCompatibleRenderPassDescriptor(); + m_releasePool << m_rp; + m_sc->setRenderPassDescriptor(m_rp); + + m_vbuf = r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)); + m_releasePool << m_vbuf; + m_vbuf->build(); + + m_ubuf = r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4); + m_releasePool << m_ubuf; + m_ubuf->build(); + + QImage image = QImage(QLatin1String(":/qt256.png")).convertToFormat(QImage::Format_RGBA8888); + m_tex = r->newTexture(QRhiTexture::RGBA8, image.size()); + m_releasePool << m_tex; + m_tex->build(); + + m_sampler = r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None, + QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge); + m_releasePool << m_sampler; + m_sampler->build(); + + m_srb = r->newShaderResourceBindings(); + m_releasePool << m_srb; + m_srb->setBindings({ + QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, m_ubuf), + QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, m_tex, m_sampler) + }); + m_srb->build(); + + m_ps = r->newGraphicsPipeline(); + m_releasePool << m_ps; + + m_ps->setDepthTest(true); + m_ps->setDepthWrite(true); + m_ps->setDepthOp(QRhiGraphicsPipeline::Less); + + m_ps->setCullMode(QRhiGraphicsPipeline::Back); + m_ps->setFrontFace(QRhiGraphicsPipeline::CCW); + + m_ps->setShaderStages({ + { QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) }, + { QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) } + }); + + QRhiVertexInputLayout inputLayout; + inputLayout.setBindings({ + { 3 * sizeof(float) }, + { 2 * sizeof(float) } + }); + inputLayout.setAttributes({ + { 0, 0, QRhiVertexInputAttribute::Float3, 0 }, + { 1, 1, QRhiVertexInputAttribute::Float2, 0 } + }); + + m_ps->setVertexInputLayout(inputLayout); + m_ps->setShaderResourceBindings(m_srb); + m_ps->setRenderPassDescriptor(m_rp); + + m_ps->build(); + + m_initialUpdates = r->nextResourceUpdateBatch(); + + m_initialUpdates->uploadStaticBuffer(m_vbuf, cube); + + qint32 flip = 0; + m_initialUpdates->updateDynamicBuffer(m_ubuf, 64, 4, &flip); + + m_initialUpdates->uploadTexture(m_tex, image); +} + +void Renderer::releaseSwapChain() +{ + if (m_hasSwapChain) { + m_hasSwapChain = false; + m_sc->release(); + } +} + +void Renderer::releaseResources() +{ + qDeleteAll(m_releasePool); + m_releasePool.clear(); + + delete m_sc; + m_sc = nullptr; +} + +void Renderer::render(bool newlyExposed, bool wakeBeforePresent) +{ + // This function handles both resizing and rendering. Resizes have some + // complications due to the threaded model (check exposeEvent and + // sendSyncSurfaceSize) but don't have to worry about that in here. + + auto buildOrResizeSwapChain = [this] { + qDebug() << "renderer" << this << "build or resize swapchain for window" << window; + const QSize outputSize = m_sc->surfacePixelSize(); + qDebug() << " size is" << outputSize; + m_ds->setPixelSize(outputSize); + m_ds->build(); + m_hasSwapChain = m_sc->buildOrResize(); + m_proj = r->clipSpaceCorrMatrix(); + m_proj.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 100.0f); + m_proj.translate(0, 0, -4); + }; + + auto wakeUpIfNeeded = [wakeBeforePresent, this] { + // make sure the main/gui thread is not blocked when issuing the Present (or equivalent) + if (wakeBeforePresent) { + thread->cond.wakeOne(); + thread->mutex.unlock(); + } + }; + + const QSize surfaceSize = m_sc->surfacePixelSize(); + if (surfaceSize.isEmpty()) { + wakeUpIfNeeded(); + return; + } + + if (newlyExposed || m_sc->currentPixelSize() != surfaceSize) + buildOrResizeSwapChain(); + + if (!m_hasSwapChain) { + wakeUpIfNeeded(); + return; + } + + QRhi::FrameOpResult result = r->beginFrame(m_sc); + if (result == QRhi::FrameOpSwapChainOutOfDate) { + buildOrResizeSwapChain(); + if (!m_hasSwapChain) { + wakeUpIfNeeded(); + return; + } + result = r->beginFrame(m_sc); + } + if (result != QRhi::FrameOpSuccess) { + wakeUpIfNeeded(); + return; + } + + QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer(); + const QSize outputSize = m_sc->currentPixelSize(); + + QRhiResourceUpdateBatch *u = r->nextResourceUpdateBatch(); + if (m_initialUpdates) { + u->merge(m_initialUpdates); + m_initialUpdates->release(); + m_initialUpdates = nullptr; + } + + m_rotation += 1.0f; + QMatrix4x4 mvp = m_proj; + mvp.scale(0.5f); + mvp.rotate(m_rotation, m_rotationAxis == 0 ? 1 : 0, m_rotationAxis == 1 ? 1 : 0, m_rotationAxis == 2 ? 1 : 0); + u->updateDynamicBuffer(m_ubuf, 0, 64, mvp.constData()); + + cb->beginPass(m_sc->currentFrameRenderTarget(), + QColor::fromRgbF(float(m_bgColor.redF()), float(m_bgColor.greenF()), float(m_bgColor.blueF()), 1.0f), + { 1.0f, 0 }, + u); + + cb->setGraphicsPipeline(m_ps); + cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); + cb->setShaderResources(); + const QRhiCommandBuffer::VertexInput vbufBindings[] = { + { m_vbuf, 0 }, + { m_vbuf, 36 * 3 * sizeof(float) } + }; + cb->setVertexInput(0, 2, vbufBindings); + cb->draw(36); + + cb->endPass(); + + wakeUpIfNeeded(); + + r->endFrame(m_sc); + + m_frameCount += 1; + if ((m_frameCount % 300) == 0) { + const QRhiProfiler::CpuTime ff = r->profiler()->frameToFrameTimes(m_sc); + const QRhiProfiler::CpuTime be = r->profiler()->frameBuildTimes(m_sc); + const QRhiProfiler::GpuTime gp = r->profiler()->gpuFrameTimes(m_sc); + if (r->isFeatureSupported(QRhi::Timestamps)) { + qDebug("[renderer %p] frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. " + "gpu frame time: min %f max %f avg %f", + this, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime, + gp.minTime, gp.maxTime, gp.avgTime); + } else { + qDebug("[renderer %p] frame-to-frame: min %lld max %lld avg %f. " + "frame build: min %lld max %lld avg %f. ", + this, + ff.minTime, ff.maxTime, ff.avgTime, + be.minTime, be.maxTime, be.avgTime); + } + } +} + +void Renderer::sendInit() +{ // main thread + InitEvent *e = new InitEvent; + thread->eventQueue.addEvent(e); +} + +void Renderer::sendRender(bool newlyExposed) +{ // main thread + RequestRenderEvent *e = new RequestRenderEvent(newlyExposed); + thread->eventQueue.addEvent(e); +} + +void Renderer::sendSurfaceGoingAway() +{ // main thread + SurfaceCleanupEvent *e = new SurfaceCleanupEvent; + + // cannot let this thread to proceed with tearing down the native window + // before the render thread completes the swapchain release + thread->mutex.lock(); + + thread->eventQueue.addEvent(e); + + thread->cond.wait(&thread->mutex); + thread->mutex.unlock(); +} + +void Renderer::sendSyncSurfaceSize() +{ // main thread + SyncSurfaceSizeEvent *e = new SyncSurfaceSizeEvent; + // must block to prevent surface size mess. the render thread will do a + // full rendering round before it unlocks which is good since it can thus + // pick up and the surface (window) size atomically. + thread->mutex.lock(); + thread->eventQueue.addEvent(e); + thread->cond.wait(&thread->mutex); + thread->mutex.unlock(); +} + +struct WindowAndRenderer +{ + QWindow *window; + Renderer *renderer; +}; + +QVector<WindowAndRenderer> windows; + +void createWindow() +{ + static QColor colors[] = { Qt::red, Qt::green, Qt::blue, Qt::yellow, Qt::cyan, Qt::gray }; + const int n = windows.count(); + Window *w = new Window(QString::asprintf("Window+Thread #%d (%s)", n, qPrintable(graphicsApiName())), graphicsApi); + Renderer *renderer = new Renderer(w, colors[n % 6], n % 3);; + QObject::connect(w, &Window::initRequested, w, [renderer] { + renderer->sendInit(); + }); + QObject::connect(w, &Window::renderRequested, w, [w, renderer](bool newlyExposed) { + renderer->sendRender(newlyExposed); + w->requestUpdate(); + }); + QObject::connect(w, &Window::surfaceGoingAway, w, [renderer] { + renderer->sendSurfaceGoingAway(); + }); + QObject::connect(w, &Window::syncSurfaceSizeRequested, w, [renderer] { + renderer->sendSyncSurfaceSize(); + }); + windows.append({ w, renderer }); + w->show(); +} + +void closeWindow() +{ + WindowAndRenderer wr = windows.takeLast(); + delete wr.renderer; + delete wr.window; +} + +int main(int argc, char **argv) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication app(argc, argv); + +#if defined(Q_OS_WIN) + graphicsApi = D3D11; +#elif defined(Q_OS_DARWIN) + 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 d3dOption({ "d", "d3d11" }, QLatin1String("Direct3D 11")); + cmdLineParser.addOption(d3dOption); + QCommandLineOption mtlOption({ "m", "metal" }, QLatin1String("Metal")); + cmdLineParser.addOption(mtlOption); + cmdLineParser.process(app); + if (cmdLineParser.isSet(glOption)) + graphicsApi = OpenGL; + if (cmdLineParser.isSet(vkOption)) + graphicsApi = Vulkan; + if (cmdLineParser.isSet(d3dOption)) + graphicsApi = D3D11; + if (cmdLineParser.isSet(mtlOption)) + graphicsApi = Metal; + + 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())); + +#if QT_CONFIG(vulkan) + instance = new QVulkanInstance; + if (graphicsApi == Vulkan) { +#ifndef Q_OS_ANDROID + instance->setLayers({ "VK_LAYER_LUNARG_standard_validation" }); +#else + instance->setLayers(QByteArrayList() + << "VK_LAYER_GOOGLE_threading" + << "VK_LAYER_LUNARG_parameter_validation" + << "VK_LAYER_LUNARG_object_tracker" + << "VK_LAYER_LUNARG_core_validation" + << "VK_LAYER_LUNARG_image" + << "VK_LAYER_LUNARG_swapchain" + << "VK_LAYER_GOOGLE_unique_objects"); +#endif + if (!instance->create()) { + qWarning("Failed to create Vulkan instance, switching to OpenGL"); + graphicsApi = OpenGL; + } + } +#endif + + int winCount = 0; + QWidget w; + w.resize(800, 600); + w.setWindowTitle(QCoreApplication::applicationName() + QLatin1String(" - ") + graphicsApiName()); + QVBoxLayout *layout = new QVBoxLayout(&w); + + QPlainTextEdit *info = new QPlainTextEdit( + QLatin1String("This application tests rendering on a separate thread per window, with dedicated QRhi instances and resources. " + "\n\nThis is the same concept as the Qt Quick Scenegraph's threaded render loop. This should allow rendering to the different windows " + "without unintentionally throttling each other's threads." + "\n\nUsing API: ") + graphicsApiName()); + info->setReadOnly(true); + layout->addWidget(info); + QLabel *label = new QLabel(QLatin1String("Window and thread count: 0")); + layout->addWidget(label); + QPushButton *btn = new QPushButton(QLatin1String("New window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] { + winCount += 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + createWindow(); + }); + layout->addWidget(btn); + btn = new QPushButton(QLatin1String("Close window")); + QObject::connect(btn, &QPushButton::clicked, btn, [label, &winCount] { + if (winCount > 0) { + winCount -= 1; + label->setText(QString::asprintf("Window count: %d", winCount)); + closeWindow(); + } + }); + layout->addWidget(btn); + w.show(); + + int result = app.exec(); + + for (const WindowAndRenderer &wr : windows) { + delete wr.renderer; + delete wr.window; + } + +#if QT_CONFIG(vulkan) + delete instance; +#endif + + return result; +} |