mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-04 16:25:27 +08:00
qt 6.5.1 original
This commit is contained in:
33
tests/manual/rhi/rhiwidget/CMakeLists.txt
Normal file
33
tests/manual/rhi/rhiwidget/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
qt_internal_add_manual_test(rhiwidget
|
||||
GUI
|
||||
SOURCES
|
||||
examplewidget.cpp examplewidget.h
|
||||
rhiwidget.cpp rhiwidget.h rhiwidget_p.h
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::GuiPrivate
|
||||
Qt::Widgets
|
||||
Qt::WidgetsPrivate
|
||||
)
|
||||
|
||||
set_source_files_properties("../shared/texture.vert.qsb"
|
||||
PROPERTIES QT_RESOURCE_ALIAS "texture.vert.qsb"
|
||||
)
|
||||
set_source_files_properties("../shared/texture.frag.qsb"
|
||||
PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
|
||||
)
|
||||
set(rhiwidget_resource_files
|
||||
"../shared/texture.vert.qsb"
|
||||
"../shared/texture.frag.qsb"
|
||||
)
|
||||
|
||||
qt_internal_add_resource(rhiwidget "rhiwidget"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${rhiwidget_resource_files}
|
||||
)
|
182
tests/manual/rhi/rhiwidget/examplewidget.cpp
Normal file
182
tests/manual/rhi/rhiwidget/examplewidget.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "examplewidget.h"
|
||||
#include "../shared/cube.h"
|
||||
#include <QFile>
|
||||
#include <QPainter>
|
||||
|
||||
static const QSize CUBE_TEX_SIZE(512, 512);
|
||||
|
||||
ExampleRhiWidget::ExampleRhiWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QRhiWidget(parent, f)
|
||||
{
|
||||
setDebugLayer(true);
|
||||
}
|
||||
|
||||
void ExampleRhiWidget::initialize(QRhi *rhi, QRhiTexture *outputTexture)
|
||||
{
|
||||
if (m_rhi != rhi) {
|
||||
m_rt.reset();
|
||||
m_rp.reset();
|
||||
m_ds.reset();
|
||||
scene.vbuf.reset();
|
||||
} else if (m_output != outputTexture) {
|
||||
m_rt.reset();
|
||||
m_rp.reset();
|
||||
}
|
||||
|
||||
m_rhi = rhi;
|
||||
m_output = outputTexture;
|
||||
|
||||
if (!m_ds) {
|
||||
m_ds.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_output->pixelSize()));
|
||||
m_ds->create();
|
||||
} else if (m_ds->pixelSize() != m_output->pixelSize()) {
|
||||
m_ds->setPixelSize(m_output->pixelSize());
|
||||
m_ds->create();
|
||||
}
|
||||
|
||||
if (!m_rt) {
|
||||
m_rt.reset(m_rhi->newTextureRenderTarget({ { m_output }, m_ds.data() }));
|
||||
m_rp.reset(m_rt->newCompatibleRenderPassDescriptor());
|
||||
m_rt->setRenderPassDescriptor(m_rp.data());
|
||||
m_rt->create();
|
||||
}
|
||||
|
||||
if (!scene.vbuf) {
|
||||
initScene();
|
||||
updateCubeTexture();
|
||||
}
|
||||
|
||||
const QSize outputSize = m_output->pixelSize();
|
||||
scene.mvp = m_rhi->clipSpaceCorrMatrix();
|
||||
scene.mvp.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f);
|
||||
scene.mvp.translate(0, 0, -4);
|
||||
updateMvp();
|
||||
}
|
||||
|
||||
void ExampleRhiWidget::updateMvp()
|
||||
{
|
||||
QMatrix4x4 mvp = scene.mvp * QMatrix4x4(QQuaternion::fromEulerAngles(QVector3D(30, itemData.cubeRotation, 0)).toRotationMatrix());
|
||||
if (!scene.resourceUpdates)
|
||||
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||
scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.data(), 0, 64, mvp.constData());
|
||||
}
|
||||
|
||||
void ExampleRhiWidget::updateCubeTexture()
|
||||
{
|
||||
QImage image(CUBE_TEX_SIZE, QImage::Format_RGBA8888);
|
||||
const QRect r(QPoint(0, 0), CUBE_TEX_SIZE);
|
||||
QPainter p(&image);
|
||||
p.fillRect(r, QGradient::DeepBlue);
|
||||
QFont font;
|
||||
font.setPointSize(24);
|
||||
p.setFont(font);
|
||||
p.drawText(r, itemData.cubeText);
|
||||
p.end();
|
||||
|
||||
if (!scene.resourceUpdates)
|
||||
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||
scene.resourceUpdates->uploadTexture(scene.cubeTex.data(), image);
|
||||
}
|
||||
|
||||
static QShader getShader(const QString &name)
|
||||
{
|
||||
QFile f(name);
|
||||
if (f.open(QIODevice::ReadOnly))
|
||||
return QShader::fromSerialized(f.readAll());
|
||||
|
||||
return QShader();
|
||||
}
|
||||
|
||||
void ExampleRhiWidget::initScene()
|
||||
{
|
||||
scene.vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube)));
|
||||
scene.vbuf->create();
|
||||
|
||||
scene.resourceUpdates = m_rhi->nextResourceUpdateBatch();
|
||||
scene.resourceUpdates->uploadStaticBuffer(scene.vbuf.data(), cube);
|
||||
|
||||
scene.ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68));
|
||||
scene.ubuf->create();
|
||||
|
||||
const qint32 flip = 0;
|
||||
scene.resourceUpdates->updateDynamicBuffer(scene.ubuf.data(), 64, 4, &flip);
|
||||
|
||||
scene.cubeTex.reset(m_rhi->newTexture(QRhiTexture::RGBA8, CUBE_TEX_SIZE));
|
||||
scene.cubeTex->create();
|
||||
|
||||
scene.sampler.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
|
||||
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
|
||||
scene.sampler->create();
|
||||
|
||||
scene.srb.reset(m_rhi->newShaderResourceBindings());
|
||||
scene.srb->setBindings({
|
||||
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, scene.ubuf.data()),
|
||||
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, scene.cubeTex.data(), scene.sampler.data())
|
||||
});
|
||||
scene.srb->create();
|
||||
|
||||
scene.ps.reset(m_rhi->newGraphicsPipeline());
|
||||
scene.ps->setDepthTest(true);
|
||||
scene.ps->setDepthWrite(true);
|
||||
scene.ps->setDepthOp(QRhiGraphicsPipeline::Less);
|
||||
scene.ps->setCullMode(QRhiGraphicsPipeline::Back);
|
||||
scene.ps->setFrontFace(QRhiGraphicsPipeline::CCW);
|
||||
QShader vs = getShader(QLatin1String(":/texture.vert.qsb"));
|
||||
Q_ASSERT(vs.isValid());
|
||||
QShader fs = getShader(QLatin1String(":/texture.frag.qsb"));
|
||||
Q_ASSERT(fs.isValid());
|
||||
scene.ps->setShaderStages({
|
||||
{ QRhiShaderStage::Vertex, vs },
|
||||
{ QRhiShaderStage::Fragment, fs }
|
||||
});
|
||||
QRhiVertexInputLayout inputLayout;
|
||||
inputLayout.setBindings({
|
||||
{ 3 * sizeof(float) },
|
||||
{ 2 * sizeof(float) }
|
||||
});
|
||||
inputLayout.setAttributes({
|
||||
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
|
||||
{ 1, 1, QRhiVertexInputAttribute::Float2, 0 }
|
||||
});
|
||||
scene.ps->setVertexInputLayout(inputLayout);
|
||||
scene.ps->setShaderResourceBindings(scene.srb.data());
|
||||
scene.ps->setRenderPassDescriptor(m_rp.data());
|
||||
scene.ps->create();
|
||||
}
|
||||
|
||||
void ExampleRhiWidget::render(QRhiCommandBuffer *cb)
|
||||
{
|
||||
if (itemData.cubeRotationDirty) {
|
||||
itemData.cubeRotationDirty = false;
|
||||
updateMvp();
|
||||
}
|
||||
|
||||
if (itemData.cubeTextDirty) {
|
||||
itemData.cubeTextDirty = false;
|
||||
updateCubeTexture();
|
||||
}
|
||||
|
||||
QRhiResourceUpdateBatch *rub = scene.resourceUpdates;
|
||||
if (rub)
|
||||
scene.resourceUpdates = nullptr;
|
||||
|
||||
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f);
|
||||
|
||||
cb->beginPass(m_rt.data(), clearColor, { 1.0f, 0 }, rub);
|
||||
|
||||
cb->setGraphicsPipeline(scene.ps.data());
|
||||
const QSize outputSize = m_output->pixelSize();
|
||||
cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height()));
|
||||
cb->setShaderResources();
|
||||
const QRhiCommandBuffer::VertexInput vbufBindings[] = {
|
||||
{ scene.vbuf.data(), 0 },
|
||||
{ scene.vbuf.data(), quint32(36 * 3 * sizeof(float)) }
|
||||
};
|
||||
cb->setVertexInput(0, 2, vbufBindings);
|
||||
cb->draw(36);
|
||||
|
||||
cb->endPass();
|
||||
}
|
66
tests/manual/rhi/rhiwidget/examplewidget.h
Normal file
66
tests/manual/rhi/rhiwidget/examplewidget.h
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef EXAMPLEWIDGET_H
|
||||
#define EXAMPLEWIDGET_H
|
||||
|
||||
#include "rhiwidget.h"
|
||||
#include <QtGui/private/qrhi_p.h>
|
||||
|
||||
class ExampleRhiWidget : public QRhiWidget
|
||||
{
|
||||
public:
|
||||
ExampleRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
|
||||
|
||||
void initialize(QRhi *rhi, QRhiTexture *outputTexture) override;
|
||||
void render(QRhiCommandBuffer *cb) override;
|
||||
|
||||
void setCubeTextureText(const QString &s)
|
||||
{
|
||||
if (itemData.cubeText == s)
|
||||
return;
|
||||
itemData.cubeText = s;
|
||||
itemData.cubeTextDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void setCubeRotation(float r)
|
||||
{
|
||||
if (itemData.cubeRotation == r)
|
||||
return;
|
||||
itemData.cubeRotation = r;
|
||||
itemData.cubeRotationDirty = true;
|
||||
update();
|
||||
}
|
||||
|
||||
private:
|
||||
QRhi *m_rhi = nullptr;
|
||||
QRhiTexture *m_output = nullptr;
|
||||
QScopedPointer<QRhiRenderBuffer> m_ds;
|
||||
QScopedPointer<QRhiTextureRenderTarget> m_rt;
|
||||
QScopedPointer<QRhiRenderPassDescriptor> m_rp;
|
||||
|
||||
struct {
|
||||
QRhiResourceUpdateBatch *resourceUpdates = nullptr;
|
||||
QScopedPointer<QRhiBuffer> vbuf;
|
||||
QScopedPointer<QRhiBuffer> ubuf;
|
||||
QScopedPointer<QRhiShaderResourceBindings> srb;
|
||||
QScopedPointer<QRhiGraphicsPipeline> ps;
|
||||
QScopedPointer<QRhiSampler> sampler;
|
||||
QScopedPointer<QRhiTexture> cubeTex;
|
||||
QMatrix4x4 mvp;
|
||||
} scene;
|
||||
|
||||
void initScene();
|
||||
void updateMvp();
|
||||
void updateCubeTexture();
|
||||
|
||||
struct {
|
||||
QString cubeText;
|
||||
bool cubeTextDirty = false;
|
||||
float cubeRotation = 0.0f;
|
||||
bool cubeRotationDirty = false;
|
||||
} itemData;
|
||||
};
|
||||
|
||||
#endif
|
97
tests/manual/rhi/rhiwidget/main.cpp
Normal file
97
tests/manual/rhi/rhiwidget/main.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QSlider>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
#include <QFileDialog>
|
||||
#include "examplewidget.h"
|
||||
|
||||
static const bool TEST_OFFSCREEN_GRAB = false;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
qputenv("QSG_INFO", "1");
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
|
||||
QLineEdit *edit = new QLineEdit(QLatin1String("Text on cube"));
|
||||
QSlider *slider = new QSlider(Qt::Horizontal);
|
||||
ExampleRhiWidget *rw = new ExampleRhiWidget;
|
||||
|
||||
QObject::connect(edit, &QLineEdit::textChanged, edit, [edit, rw] {
|
||||
rw->setCubeTextureText(edit->text());
|
||||
});
|
||||
|
||||
slider->setMinimum(0);
|
||||
slider->setMaximum(360);
|
||||
QObject::connect(slider, &QSlider::valueChanged, slider, [slider, rw] {
|
||||
rw->setCubeRotation(slider->value());
|
||||
});
|
||||
|
||||
QPushButton *btn = new QPushButton(QLatin1String("Grab to image"));
|
||||
QObject::connect(btn, &QPushButton::clicked, btn, [rw] {
|
||||
QImage image = rw->grabTexture();
|
||||
qDebug() << image;
|
||||
if (!image.isNull()) {
|
||||
QFileDialog fd(rw->parentWidget());
|
||||
fd.setAcceptMode(QFileDialog::AcceptSave);
|
||||
fd.setDefaultSuffix("png");
|
||||
fd.selectFile("test.png");
|
||||
if (fd.exec() == QDialog::Accepted)
|
||||
image.save(fd.selectedFiles().first());
|
||||
}
|
||||
});
|
||||
QHBoxLayout *btnLayout = new QHBoxLayout;
|
||||
btnLayout->addWidget(btn);
|
||||
QCheckBox *cbExplicitSize = new QCheckBox(QLatin1String("Use explicit size"));
|
||||
QObject::connect(cbExplicitSize, &QCheckBox::stateChanged, cbExplicitSize, [cbExplicitSize, rw] {
|
||||
if (cbExplicitSize->isChecked())
|
||||
rw->setExplicitSize(QSize(128, 128));
|
||||
else
|
||||
rw->setExplicitSize(QSize());
|
||||
});
|
||||
btnLayout->addWidget(cbExplicitSize);
|
||||
QPushButton *btnMakeWindow = new QPushButton(QLatin1String("Make top-level window"));
|
||||
QObject::connect(btnMakeWindow, &QPushButton::clicked, btnMakeWindow, [rw, btnMakeWindow, layout] {
|
||||
if (rw->parentWidget()) {
|
||||
rw->setParent(nullptr);
|
||||
rw->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
rw->show();
|
||||
btnMakeWindow->setText(QLatin1String("Make child widget"));
|
||||
} else {
|
||||
rw->setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
layout->addWidget(rw);
|
||||
btnMakeWindow->setText(QLatin1String("Make top-level window"));
|
||||
}
|
||||
});
|
||||
btnLayout->addWidget(btnMakeWindow);
|
||||
|
||||
layout->addWidget(edit);
|
||||
QHBoxLayout *sliderLayout = new QHBoxLayout;
|
||||
sliderLayout->addWidget(new QLabel(QLatin1String("Cube rotation")));
|
||||
sliderLayout->addWidget(slider);
|
||||
layout->addLayout(sliderLayout);
|
||||
layout->addLayout(btnLayout);
|
||||
layout->addWidget(rw);
|
||||
|
||||
rw->setCubeTextureText(edit->text());
|
||||
|
||||
if (TEST_OFFSCREEN_GRAB) {
|
||||
rw->resize(320, 200);
|
||||
rw->grabTexture().save("offscreen_grab.png");
|
||||
}
|
||||
|
||||
QWidget w;
|
||||
w.setLayout(layout);
|
||||
w.resize(1280, 720);
|
||||
w.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
545
tests/manual/rhi/rhiwidget/rhiwidget.cpp
Normal file
545
tests/manual/rhi/rhiwidget/rhiwidget.cpp
Normal file
@ -0,0 +1,545 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "rhiwidget_p.h"
|
||||
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include <qpa/qplatformintegration.h>
|
||||
#include <private/qwidgetrepaintmanager_p.h>
|
||||
|
||||
/*!
|
||||
\class QRhiWidget
|
||||
\inmodule QtWidgets
|
||||
\since 6.x
|
||||
|
||||
\brief The QRhiWidget class is a widget for rendering 3D graphics via an
|
||||
accelerated grapics API, such as Vulkan, Metal, or Direct 3D.
|
||||
|
||||
QRhiWidget provides functionality for displaying 3D content rendered through
|
||||
the QRhi APIs within a QWidget-based application.
|
||||
|
||||
QRhiWidget is expected to be subclassed. To render into the 2D texture that
|
||||
is implicitly created and managed by the QRhiWidget, subclasses should
|
||||
reimplement the virtual functions initialize() and render().
|
||||
|
||||
The size of the texture will by default adapt to the size of the item. If a
|
||||
fixed size is preferred, set an explicit size specified in pixels by
|
||||
calling setExplicitSize().
|
||||
|
||||
The QRhi for the widget's top-level window is configured to use a platform
|
||||
specific backend and graphics API by default: Metal on macOS and iOS,
|
||||
Direct 3D 11 on Windows, OpenGL otherwise. Call setApi() to override this.
|
||||
|
||||
\note A single widget window can only use one QRhi backend, and so graphics
|
||||
API. If two QRhiWidget or QQuickWidget widgets in the window's widget
|
||||
hierarchy request different APIs, only one of them will function correctly.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs a widget which is a child of \a parent, with widget flags set to \a f.
|
||||
*/
|
||||
QRhiWidget::QRhiWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QWidget(*(new QRhiWidgetPrivate), parent, f)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)))
|
||||
qWarning("QRhiWidget: QRhi is not supported on this platform.");
|
||||
else
|
||||
d->setRenderToTexture();
|
||||
|
||||
d->config.setEnabled(true);
|
||||
#if defined(Q_OS_DARWIN)
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
|
||||
#elif defined(Q_OS_WIN)
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
|
||||
#else
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Destructor.
|
||||
*/
|
||||
QRhiWidget::~QRhiWidget()
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
// rhi resources must be destroyed here, cannot be left to the private dtor
|
||||
delete d->t;
|
||||
d->offscreenRenderer.reset();
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles resize events that are passed in the \a e event parameter. Calls
|
||||
the virtual function initialize().
|
||||
|
||||
\note Avoid overriding this function in derived classes. If that is not
|
||||
feasible, make sure that QRhiWidget's implementation is invoked too.
|
||||
Otherwise the underlying texture object and related resources will not get
|
||||
resized properly and will lead to incorrect rendering.
|
||||
*/
|
||||
void QRhiWidget::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
|
||||
if (e->size().isEmpty()) {
|
||||
d->noSize = true;
|
||||
return;
|
||||
}
|
||||
d->noSize = false;
|
||||
|
||||
d->sendPaintEvent(QRect(QPoint(0, 0), size()));
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles paint events.
|
||||
|
||||
Calling QWidget::update() will lead to sending a paint event \a e, and thus
|
||||
invoking this function. (NB this is asynchronous and will happen at some
|
||||
point after returning from update()). This function will then, after some
|
||||
preparation, call the virtual render() to update the contents of the
|
||||
QRhiWidget's associated texture. The widget's top-level window will then
|
||||
composite the texture with the rest of the window.
|
||||
*/
|
||||
void QRhiWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
if (!updatesEnabled() || d->noSize)
|
||||
return;
|
||||
|
||||
d->ensureRhi();
|
||||
if (!d->rhi) {
|
||||
qWarning("QRhiWidget: No QRhi");
|
||||
return;
|
||||
}
|
||||
|
||||
const QSize prevSize = d->t ? d->t->pixelSize() : QSize();
|
||||
d->ensureTexture();
|
||||
if (!d->t)
|
||||
return;
|
||||
if (d->t->pixelSize() != prevSize)
|
||||
initialize(d->rhi, d->t);
|
||||
|
||||
QRhiCommandBuffer *cb = nullptr;
|
||||
d->rhi->beginOffscreenFrame(&cb);
|
||||
render(cb);
|
||||
d->rhi->endOffscreenFrame();
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
bool QRhiWidget::event(QEvent *e)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
switch (e->type()) {
|
||||
case QEvent::WindowChangeInternal:
|
||||
// the QRhi will almost certainly change, prevent texture() from
|
||||
// returning the existing QRhiTexture in the meantime
|
||||
d->textureInvalid = true;
|
||||
break;
|
||||
case QEvent::Show:
|
||||
if (isVisible())
|
||||
d->sendPaintEvent(QRect(QPoint(0, 0), size()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWidget::event(e);
|
||||
}
|
||||
|
||||
QPlatformBackingStoreRhiConfig QRhiWidgetPrivate::rhiConfig() const
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
void QRhiWidgetPrivate::ensureRhi()
|
||||
{
|
||||
Q_Q(QRhiWidget);
|
||||
// the QRhi and infrastructure belongs to the top-level widget, not to this widget
|
||||
QWidget *tlw = q->window();
|
||||
QWidgetPrivate *wd = get(tlw);
|
||||
|
||||
QRhi *currentRhi = nullptr;
|
||||
if (QWidgetRepaintManager *repaintManager = wd->maybeRepaintManager())
|
||||
currentRhi = repaintManager->rhi();
|
||||
|
||||
if (currentRhi && currentRhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(config.api())) {
|
||||
qWarning("The top-level window is already using another graphics API for composition, "
|
||||
"'%s' is not compatible with this widget",
|
||||
currentRhi->backendName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRhi && rhi && rhi != currentRhi) {
|
||||
// the texture belongs to the old rhi, drop it, this will also lead to
|
||||
// initialize() being called again
|
||||
delete t;
|
||||
t = nullptr;
|
||||
// if previously we created our own but now get a QRhi from the
|
||||
// top-level, then drop what we have and start using the top-level's
|
||||
if (rhi == offscreenRenderer.rhi())
|
||||
offscreenRenderer.reset();
|
||||
}
|
||||
|
||||
rhi = currentRhi;
|
||||
}
|
||||
|
||||
void QRhiWidgetPrivate::ensureTexture()
|
||||
{
|
||||
Q_Q(QRhiWidget);
|
||||
|
||||
QSize newSize = explicitSize;
|
||||
if (newSize.isEmpty())
|
||||
newSize = q->size() * q->devicePixelRatio();
|
||||
|
||||
if (!t) {
|
||||
if (!rhi->isTextureFormatSupported(format))
|
||||
qWarning("QRhiWidget: The requested texture format is not supported by the graphics API implementation");
|
||||
t = rhi->newTexture(format, newSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
|
||||
if (!t->create()) {
|
||||
qWarning("Failed to create backing texture for QRhiWidget");
|
||||
delete t;
|
||||
t = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (t->pixelSize() != newSize) {
|
||||
t->setPixelSize(newSize);
|
||||
if (!t->create())
|
||||
qWarning("Failed to rebuild texture for QRhiWidget after resizing");
|
||||
}
|
||||
|
||||
textureInvalid = false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\return the currently set graphics API (QRhi backend).
|
||||
|
||||
\sa setApi()
|
||||
*/
|
||||
QRhiWidget::Api QRhiWidget::api() const
|
||||
{
|
||||
Q_D(const QRhiWidget);
|
||||
switch (d->config.api()) {
|
||||
case QPlatformBackingStoreRhiConfig::OpenGL:
|
||||
return OpenGL;
|
||||
case QPlatformBackingStoreRhiConfig::Metal:
|
||||
return Metal;
|
||||
case QPlatformBackingStoreRhiConfig::Vulkan:
|
||||
return Vulkan;
|
||||
case QPlatformBackingStoreRhiConfig::D3D11:
|
||||
return D3D11;
|
||||
default:
|
||||
return Null;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the graphics API and QRhi backend to use to \a api.
|
||||
|
||||
The default value depends on the platform: Metal on macOS and iOS, Direct
|
||||
3D 11 on Windows, OpenGL otherwise.
|
||||
|
||||
\note This function must be called early enough, before the widget is added
|
||||
to a widget hierarchy and displayed on screen. For example, aim to call the
|
||||
function for the subclass constructor. If called too late, the function
|
||||
will have no effect.
|
||||
|
||||
The \a api can only be set once for the widget and its top-level window,
|
||||
once it is done and takes effect, the window can only use that API and QRhi
|
||||
backend to render. Attempting to set another value, or to add another
|
||||
QRhiWidget with a different \a api will not function as expected.
|
||||
|
||||
\sa setTextureFormat(), setDebugLayer(), api()
|
||||
*/
|
||||
void QRhiWidget::setApi(Api api)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
switch (api) {
|
||||
case OpenGL:
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
|
||||
break;
|
||||
case Metal:
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
|
||||
break;
|
||||
case Vulkan:
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
|
||||
break;
|
||||
case D3D11:
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
|
||||
break;
|
||||
default:
|
||||
d->config.setApi(QPlatformBackingStoreRhiConfig::Null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\return true if a debug or validation layer will be requested if applicable
|
||||
to the graphics API in use.
|
||||
|
||||
\sa setDebugLayer()
|
||||
*/
|
||||
bool QRhiWidget::isDebugLayerEnabled() const
|
||||
{
|
||||
Q_D(const QRhiWidget);
|
||||
return d->config.isDebugLayerEnabled();
|
||||
}
|
||||
|
||||
/*!
|
||||
Requests the debug or validation layer of the underlying graphics API
|
||||
when \a enable is true.
|
||||
|
||||
Applicable for Vulkan and Direct 3D.
|
||||
|
||||
\note This function must be called early enough, before the widget is added
|
||||
to a widget hierarchy and displayed on screen. For example, aim to call the
|
||||
function for the subclass constructor. If called too late, the function
|
||||
will have no effect.
|
||||
|
||||
By default this is disabled.
|
||||
|
||||
\sa setApi(), isDebugLayerEnabled()
|
||||
*/
|
||||
void QRhiWidget::setDebugLayer(bool enable)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
d->config.setDebugLayer(enable);
|
||||
}
|
||||
|
||||
/*!
|
||||
\return the currently set texture format.
|
||||
|
||||
The default value is QRhiTexture::RGBA8.
|
||||
|
||||
\sa setTextureFormat()
|
||||
*/
|
||||
QRhiTexture::Format QRhiWidget::textureFormat() const
|
||||
{
|
||||
Q_D(const QRhiWidget);
|
||||
return d->format;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the associated texture's \a format.
|
||||
|
||||
The default value is QRhiTexture::RGBA8. Only formats that are reported as
|
||||
supported from QRhi::isTextureFormatSupported() should be specified,
|
||||
rendering will not be functional otherwise.
|
||||
|
||||
\note This function must be called early enough, before the widget is added
|
||||
to a widget hierarchy and displayed on screen. For example, aim to call the
|
||||
function for the subclass constructor. If called too late, the function
|
||||
will have no effect.
|
||||
|
||||
\sa setApi(), textureFormat()
|
||||
*/
|
||||
void QRhiWidget::setTextureFormat(QRhiTexture::Format format)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
d->format = format;
|
||||
}
|
||||
|
||||
/*!
|
||||
\property QRhiWidget::explicitSize
|
||||
|
||||
The fixed size (in pixels) of the QRhiWidget's associated texture.
|
||||
|
||||
Only relevant when a fixed texture size is desired that does not depend on
|
||||
the widget's size.
|
||||
|
||||
By default the value is a null QSize. A null or empty QSize means that the
|
||||
texture's size follows the QRhiWidget's size. (\c{texture size} = \c{widget
|
||||
size} * \c{device pixel ratio}).
|
||||
*/
|
||||
|
||||
QSize QRhiWidget::explicitSize() const
|
||||
{
|
||||
Q_D(const QRhiWidget);
|
||||
return d->explicitSize;
|
||||
}
|
||||
|
||||
void QRhiWidget::setExplicitSize(const QSize &pixelSize)
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
if (d->explicitSize != pixelSize) {
|
||||
d->explicitSize = pixelSize;
|
||||
emit explicitSizeChanged(pixelSize);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Renders a new frame, reads the contents of the texture back, and returns it
|
||||
as a QImage.
|
||||
|
||||
When an error occurs, a null QImage is returned.
|
||||
|
||||
\note This function only supports reading back QRhiTexture::RGBA8 textures
|
||||
at the moment. For other formats, the implementer of render() should
|
||||
implement their own readback logic as they see fit.
|
||||
|
||||
The returned QImage will have a format of QImage::Format_RGBA8888.
|
||||
QRhiWidget does not know the renderer's approach to blending and
|
||||
composition, and therefore cannot know if the output has alpha
|
||||
premultiplied.
|
||||
|
||||
This function can also be called when the QRhiWidget is not added to a
|
||||
widget hierarchy belonging to an on-screen top-level window. This allows
|
||||
generating an image from a 3D rendering off-screen.
|
||||
|
||||
\sa setTextureFormat()
|
||||
*/
|
||||
QImage QRhiWidget::grabTexture()
|
||||
{
|
||||
Q_D(QRhiWidget);
|
||||
if (d->noSize)
|
||||
return QImage();
|
||||
|
||||
if (d->format != QRhiTexture::RGBA8) {
|
||||
qWarning("QRhiWidget::grabTexture() only supports RGBA8 textures");
|
||||
return QImage();
|
||||
}
|
||||
|
||||
d->ensureRhi();
|
||||
if (!d->rhi) {
|
||||
// The widget (and its parent chain, if any) may not be shown at
|
||||
// all, yet one may still want to use it for grabs. This is
|
||||
// ridiculous of course because the rendering infrastructure is
|
||||
// tied to the top-level widget that initializes upon expose, but
|
||||
// it has to be supported.
|
||||
d->offscreenRenderer.setConfig(d->config);
|
||||
// no window passed in, so no swapchain, but we get a functional QRhi which we own
|
||||
d->offscreenRenderer.create();
|
||||
d->rhi = d->offscreenRenderer.rhi();
|
||||
if (!d->rhi) {
|
||||
qWarning("QRhiWidget: Failed to create dedicated QRhi for grabbing");
|
||||
return QImage();
|
||||
}
|
||||
}
|
||||
|
||||
const QSize prevSize = d->t ? d->t->pixelSize() : QSize();
|
||||
d->ensureTexture();
|
||||
if (!d->t)
|
||||
return QImage();
|
||||
if (d->t->pixelSize() != prevSize)
|
||||
initialize(d->rhi, d->t);
|
||||
|
||||
QRhiReadbackResult readResult;
|
||||
bool readCompleted = false;
|
||||
readResult.completed = [&readCompleted] { readCompleted = true; };
|
||||
|
||||
QRhiCommandBuffer *cb = nullptr;
|
||||
d->rhi->beginOffscreenFrame(&cb);
|
||||
render(cb);
|
||||
QRhiResourceUpdateBatch *readbackBatch = d->rhi->nextResourceUpdateBatch();
|
||||
readbackBatch->readBackTexture(d->t, &readResult);
|
||||
cb->resourceUpdate(readbackBatch);
|
||||
d->rhi->endOffscreenFrame();
|
||||
|
||||
if (readCompleted) {
|
||||
QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
|
||||
readResult.pixelSize.width(), readResult.pixelSize.height(),
|
||||
QImage::Format_RGBA8888);
|
||||
QImage result = wrapperImage.copy();
|
||||
result.setDevicePixelRatio(devicePixelRatio());
|
||||
return result;
|
||||
} else {
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
return QImage();
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when the widget is initialized, when the associated texture's size
|
||||
changes, or when the QRhi and texture change for some reason.
|
||||
|
||||
The implementation should be prepared that both \a rhi and \a outputTexture
|
||||
can change between invocations of this function, although this is not
|
||||
always going to happen in practice. When the widget size changes, this
|
||||
function is called with the same \a rhi and \a outputTexture as before, but
|
||||
\a outputTexture may have been rebuilt, meaning its
|
||||
\l{QRhiTexture::pixelSize()}{size} and the underlying native texture
|
||||
resource may be different than in the last invocation.
|
||||
|
||||
One special case where the objects will be different is when performing a
|
||||
grabTexture() with a widget that is not yet shown, and then making the
|
||||
widget visible on-screen within a top-level widget. There the grab will
|
||||
happen with a dedicated QRhi that is then replaced with the top-level
|
||||
window's associated QRhi in subsequent initialize() and render()
|
||||
invocations.
|
||||
|
||||
Another, more common case is when the widget is reparented so that it
|
||||
belongs to a new top-level window. In this case \a rhi and \a outputTexture
|
||||
will definitely be different in the subsequent call to this function. Is is
|
||||
then important that all existing QRhi resources are destroyed because they
|
||||
belong to the previous QRhi that should not be used by the widget anymore.
|
||||
|
||||
Implementations will typically create or rebuild a QRhiTextureRenderTarget
|
||||
in order to allow the subsequent render() call to render into the texture.
|
||||
When a depth buffer is necessary create a QRhiRenderBuffer as well. The
|
||||
size if this must follow the size of \a outputTexture. A compact and
|
||||
efficient way for this is the following:
|
||||
|
||||
\code
|
||||
if (m_rhi != rhi) {
|
||||
// reset all resources (incl. m_ds, m_rt, m_rp)
|
||||
} else if (m_output != outputTexture) {
|
||||
// reset m_rt and m_rp
|
||||
}
|
||||
m_rhi = rhi;
|
||||
m_output = outputTexture;
|
||||
if (!m_ds) {
|
||||
// no depth-stencil buffer yet, create one
|
||||
m_ds = m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, m_output->pixelSize());
|
||||
m_ds->create();
|
||||
} else if (m_ds->pixelSize() != m_output->pixelSize()) {
|
||||
// the size has changed, update the size and rebuild
|
||||
m_ds->setPixelSize(m_output->pixelSize());
|
||||
m_ds->create();
|
||||
}
|
||||
if (!m_rt) {
|
||||
m_rt = m_rhi->newTextureRenderTarget({ { m_output }, m_ds });
|
||||
m_rp = m_rt->newCompatibleRenderPassDescriptor();
|
||||
m_rt->setRenderPassDescriptor(m_rp);
|
||||
m_rt->create();
|
||||
}
|
||||
\endcode
|
||||
|
||||
The above snippet is also prepared for \a rhi and \a outputTexture changing
|
||||
between invocations, via the checks at the beginning of the function.
|
||||
|
||||
The created resources are expected to be released in the destructor
|
||||
implementation of the subclass. \a rhi and \a outputTexture are not owned
|
||||
by, and are guaranteed to outlive the QRhiWidget.
|
||||
|
||||
\sa render()
|
||||
*/
|
||||
void QRhiWidget::initialize(QRhi *rhi, QRhiTexture *outputTexture)
|
||||
{
|
||||
Q_UNUSED(rhi);
|
||||
Q_UNUSED(outputTexture);
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when the widget contents (i.e. the contents of the texture) need
|
||||
updating.
|
||||
|
||||
There is always at least one call to initialize() before this function is
|
||||
called.
|
||||
|
||||
To request updates, call QWidget::update(). Calling update() from within
|
||||
render() will lead to updating continuously, throttled by vsync.
|
||||
|
||||
\a cb is the QRhiCommandBuffer for the current frame of the Qt Quick
|
||||
scenegraph. The function is called with a frame being recorded, but without
|
||||
an active render pass.
|
||||
|
||||
\sa initialize()
|
||||
*/
|
||||
void QRhiWidget::render(QRhiCommandBuffer *cb)
|
||||
{
|
||||
Q_UNUSED(cb);
|
||||
}
|
56
tests/manual/rhi/rhiwidget/rhiwidget.h
Normal file
56
tests/manual/rhi/rhiwidget/rhiwidget.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef RHIWIDGET_H
|
||||
#define RHIWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QtGui/private/qrhi_p.h>
|
||||
|
||||
class QRhiWidgetPrivate;
|
||||
|
||||
class QRhiWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(QRhiWidget)
|
||||
Q_PROPERTY(QSize explicitSize READ explicitSize WRITE setExplicitSize NOTIFY explicitSizeChanged)
|
||||
|
||||
public:
|
||||
QRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
|
||||
~QRhiWidget();
|
||||
|
||||
enum Api {
|
||||
OpenGL,
|
||||
Metal,
|
||||
Vulkan,
|
||||
D3D11,
|
||||
Null
|
||||
};
|
||||
|
||||
Api api() const;
|
||||
void setApi(Api api);
|
||||
|
||||
bool isDebugLayerEnabled() const;
|
||||
void setDebugLayer(bool enable);
|
||||
|
||||
QRhiTexture::Format textureFormat() const;
|
||||
void setTextureFormat(QRhiTexture::Format format);
|
||||
|
||||
QSize explicitSize() const;
|
||||
void setExplicitSize(const QSize &pixelSize);
|
||||
|
||||
virtual void initialize(QRhi *rhi, QRhiTexture *outputTexture);
|
||||
virtual void render(QRhiCommandBuffer *cb);
|
||||
|
||||
QImage grabTexture();
|
||||
|
||||
Q_SIGNALS:
|
||||
void explicitSizeChanged(const QSize &pixelSize);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
bool event(QEvent *e) override;
|
||||
};
|
||||
|
||||
#endif
|
38
tests/manual/rhi/rhiwidget/rhiwidget_p.h
Normal file
38
tests/manual/rhi/rhiwidget/rhiwidget_p.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef RHIWIDGET_P_H
|
||||
#define RHIWIDGET_P_H
|
||||
|
||||
#include "rhiwidget.h"
|
||||
|
||||
#include <private/qwidget_p.h>
|
||||
#include <private/qbackingstorerhisupport_p.h>
|
||||
|
||||
class QRhiWidgetPrivate : public QWidgetPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QRhiWidget)
|
||||
public:
|
||||
TextureData texture() const override
|
||||
{
|
||||
TextureData td;
|
||||
if (!textureInvalid)
|
||||
td.textureLeft = t;
|
||||
return td;
|
||||
}
|
||||
QPlatformBackingStoreRhiConfig rhiConfig() const override;
|
||||
|
||||
void ensureRhi();
|
||||
void ensureTexture();
|
||||
|
||||
QRhi *rhi = nullptr;
|
||||
QRhiTexture *t = nullptr;
|
||||
bool noSize = false;
|
||||
QPlatformBackingStoreRhiConfig config;
|
||||
QRhiTexture::Format format = QRhiTexture::RGBA8;
|
||||
QSize explicitSize;
|
||||
QBackingStoreRhiSupport offscreenRenderer;
|
||||
bool textureInvalid = false;
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user