qtdeclarative/examples/quick/scenegraph/customrendernode/customrender.cpp

218 lines
7.5 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "customrender.h"
#include <QSGTextureProvider>
#include <QSGRenderNode>
#include <QSGTransformNode>
#include <QQuickWindow>
#include <QFile>
#include <rhi/qrhi.h>
//![node]
class CustomRenderNode : public QSGRenderNode
{
public:
CustomRenderNode(QQuickWindow *window);
void setVertices(const QList<QVector2D> &vertices);
void prepare() override;
void render(const RenderState *state) override;
void releaseResources() override;
RenderingFlags flags() const override;
QSGRenderNode::StateFlags changedStates() const override;
protected:
QQuickWindow *m_window;
std::unique_ptr<QRhiBuffer> m_vertexBuffer;
std::unique_ptr<QRhiBuffer> m_uniformBuffer;
std::unique_ptr<QRhiShaderResourceBindings> m_resourceBindings;
std::unique_ptr<QRhiGraphicsPipeline> m_pipeline;
QList<QRhiShaderStage> m_shaders;
bool m_verticesDirty = true;
QList<QVector2D> m_vertices;
};
//![node]
CustomRenderNode::CustomRenderNode(QQuickWindow *window)
: m_window(window)
{
QFile file;
file.setFileName(":/scenegraph/customrendernode/shaders/customrender.vert.qsb");
if (!file.open(QFile::ReadOnly))
qFatal("Failed to load vertex shader");
m_shaders.append(QRhiShaderStage(QRhiShaderStage::Vertex, QShader::fromSerialized(file.readAll())));
file.close();
file.setFileName(":/scenegraph/customrendernode/shaders/customrender.frag.qsb");
if (!file.open(QFile::ReadOnly))
qFatal("Failed to load fragment shader");
m_shaders.append(QRhiShaderStage(QRhiShaderStage::Fragment, QShader::fromSerialized(file.readAll())));
}
void CustomRenderNode::setVertices(const QList<QVector2D> &vertices)
{
if (m_vertices == vertices)
return;
m_verticesDirty = true;
m_vertices = vertices;
markDirty(QSGNode::DirtyGeometry);
}
//![node-release]
void CustomRenderNode::releaseResources()
{
m_vertexBuffer.reset();
m_uniformBuffer.reset();
m_pipeline.reset();
m_resourceBindings.reset();
}
//![node-release]
//![node-flags]
QSGRenderNode::RenderingFlags CustomRenderNode::flags() const
{
// We are rendering 2D content directly into the scene graph using QRhi, no
// direct usage of a 3D API. Hence NoExternalRendering. This is a minor
// optimization.
// Additionally, the node takes the item transform into account by relying
// on projectionMatrix() and matrix() (see prepare()) and never rendering at
// other Z coordinates. Hence DepthAwareRendering. This is a potentially
// bigger optimization.
return QSGRenderNode::NoExternalRendering | QSGRenderNode::DepthAwareRendering;
}
//![node-flags]
QSGRenderNode::StateFlags CustomRenderNode::changedStates() const
{
// In Qt 6 only ViewportState and ScissorState matter, the rest is ignored.
return QSGRenderNode::StateFlag::ViewportState | QSGRenderNode::StateFlag::CullState;
}
//![node-prepare]
void CustomRenderNode::prepare()
{
QRhi *rhi = m_window->rhi();
QRhiResourceUpdateBatch *resourceUpdates = rhi->nextResourceUpdateBatch();
if (m_verticesDirty) {
m_vertexBuffer.reset();
m_verticesDirty = false;
}
if (!m_vertexBuffer) {
m_vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer,
m_vertices.count() * sizeof(QVector2D)));
m_vertexBuffer->create();
resourceUpdates->uploadStaticBuffer(m_vertexBuffer.get(), m_vertices.constData());
}
//![node-prepare]
if (!m_uniformBuffer) {
m_uniformBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68));
m_uniformBuffer->create();
}
if (!m_resourceBindings) {
m_resourceBindings.reset(rhi->newShaderResourceBindings());
m_resourceBindings->setBindings({ QRhiShaderResourceBinding::uniformBuffer(
0,
QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage,
m_uniformBuffer.get()) });
m_resourceBindings->create();
}
if (!m_pipeline) {
m_pipeline.reset(rhi->newGraphicsPipeline());
// If layer.enabled == true on our QQuickItem and layer.textureMirroring is the
// default MirrorVertically, the winding order needs to be inverted when
// isYUpInFrameBuffer == true (OpenGL). This does not happen with other backends,
// unless textureMirroring is changed so that the situation is inverted, meaning
// GL needs no adjustments while others need the opposite winding order. Here we
// choose the replicate, to a degree, what the scenegraph renderer does, but note that
// this is only correct as long as textureMirroring is not changed from the default.
m_pipeline->setFrontFace(renderTarget()->resourceType() == QRhiResource::TextureRenderTarget
&& rhi->isYUpInFramebuffer()
? QRhiGraphicsPipeline::CW
: QRhiGraphicsPipeline::CCW);
m_pipeline->setCullMode(QRhiGraphicsPipeline::Back);
m_pipeline->setTopology(QRhiGraphicsPipeline::TriangleStrip);
QRhiGraphicsPipeline::TargetBlend blend;
blend.enable = true;
m_pipeline->setTargetBlends({ blend });
m_pipeline->setShaderResourceBindings(m_resourceBindings.get());
m_pipeline->setShaderStages(m_shaders.cbegin(), m_shaders.cend());
m_pipeline->setDepthTest(true);
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({ { 2 * sizeof(float) } });
inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 } });
m_pipeline->setVertexInputLayout(inputLayout);
m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor());
m_pipeline->create();
}
const QMatrix4x4 mvp = *projectionMatrix() * *matrix();
const float opacity = inheritedOpacity();
resourceUpdates->updateDynamicBuffer(m_uniformBuffer.get(), 0, 64, mvp.constData());
resourceUpdates->updateDynamicBuffer(m_uniformBuffer.get(), 64, 4, &opacity);
commandBuffer()->resourceUpdate(resourceUpdates);
}
//![node-render]
void CustomRenderNode::render(const RenderState *)
{
QRhiCommandBuffer *cb = commandBuffer();
cb->setGraphicsPipeline(m_pipeline.get());
QSize renderTargetSize = renderTarget()->pixelSize();
cb->setViewport(QRhiViewport(0, 0, renderTargetSize.width(), renderTargetSize.height()));
cb->setShaderResources();
QRhiCommandBuffer::VertexInput vertexBindings[] = { { m_vertexBuffer.get(), 0 } };
cb->setVertexInput(0, 1, vertexBindings);
cb->draw(m_vertices.count());
}
//![node-render]
//![item-ctor]
CustomRender::CustomRender(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents, true);
connect(this, &CustomRender::verticesChanged, this, &CustomRender::update);
}
//![item-ctor]
QList<QVector2D> CustomRender::vertices() const
{
return m_vertices;
}
void CustomRender::setVertices(const QList<QVector2D> &newVertices)
{
if (m_vertices == newVertices)
return;
m_vertices = newVertices;
emit verticesChanged();
}
//![item-update]
QSGNode *CustomRender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *)
{
CustomRenderNode *node = static_cast<CustomRenderNode *>(old);
if (!node)
node = new CustomRenderNode(window());
node->setVertices(m_vertices);
return node;
}
//![item-update]