aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/quick/scenegraph/rendernode/customrenderitem.cpp28
-rw-r--r--examples/quick/scenegraph/rendernode/d3d12renderer.cpp6
-rw-r--r--examples/quick/scenegraph/rendernode/main.qml81
-rw-r--r--examples/quick/scenegraph/rendernode/openglrenderer.cpp20
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp108
-rw-r--r--src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h2
-rw-r--r--src/quick/scenegraph/coreapi/qsgrendernode.cpp103
7 files changed, 284 insertions, 64 deletions
diff --git a/examples/quick/scenegraph/rendernode/customrenderitem.cpp b/examples/quick/scenegraph/rendernode/customrenderitem.cpp
index 8f248e2ecb..9dbe8d86a0 100644
--- a/examples/quick/scenegraph/rendernode/customrenderitem.cpp
+++ b/examples/quick/scenegraph/rendernode/customrenderitem.cpp
@@ -71,23 +71,29 @@ QSGNode *CustomRenderItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
if (!ri)
return nullptr;
switch (ri->graphicsApi()) {
- case QSGRendererInterface::OpenGL:
+ case QSGRendererInterface::OpenGL:
+ Q_FALLTHROUGH();
+ case QSGRendererInterface::OpenGLRhi:
#if QT_CONFIG(opengl)
- n = new OpenGLRenderNode(this);
- break;
+ n = new OpenGLRenderNode(this);
#endif
- case QSGRendererInterface::Direct3D12:
+ break;
+
+ case QSGRendererInterface::Direct3D12: // ### Qt 6: remove
#if QT_CONFIG(d3d12)
- n = new D3D12RenderNode(this);
- break;
+ n = new D3D12RenderNode(this);
#endif
- case QSGRendererInterface::Software:
- n = new SoftwareRenderNode(this);
- break;
+ break;
+
+ case QSGRendererInterface::Software:
+ n = new SoftwareRenderNode(this);
+ break;
- default:
- return nullptr;
+ default:
+ break;
}
+ if (!n)
+ qWarning("QSGRendererInterface reports unknown graphics API %d", ri->graphicsApi());
}
return n;
diff --git a/examples/quick/scenegraph/rendernode/d3d12renderer.cpp b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp
index df0e29eb67..878b022950 100644
--- a/examples/quick/scenegraph/rendernode/d3d12renderer.cpp
+++ b/examples/quick/scenegraph/rendernode/d3d12renderer.cpp
@@ -54,6 +54,8 @@
#include <QSGRendererInterface>
#include <QFile>
+// ### Qt 6: remove
+
#if QT_CONFIG(d3d12)
D3D12RenderNode::D3D12RenderNode(QQuickItem *item)
@@ -166,8 +168,8 @@ void D3D12RenderNode::init()
psoDesc.RasterizerState = rastDesc;
psoDesc.BlendState = blendDesc;
// No depth. The correct stacking of the item is ensured by the projection matrix.
- // Do not bother with stencil since we do not apply clipping in the
- // example. If clipping is desired, render() needs to set a different PSO
+ // Note that this does not support clipping.
+ // If clipping is desired, render() needs to set a different PSO
// with stencil enabled whenever the RenderState indicates so.
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
diff --git a/examples/quick/scenegraph/rendernode/main.qml b/examples/quick/scenegraph/rendernode/main.qml
index d0ba4a4669..85060261c5 100644
--- a/examples/quick/scenegraph/rendernode/main.qml
+++ b/examples/quick/scenegraph/rendernode/main.qml
@@ -53,23 +53,55 @@ import SceneGraphRendering 2.0
Item {
Rectangle {
+ id: bg
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0; color: "steelblue" }
GradientStop { position: 1; color: "black" }
}
- CustomRenderItem {
- id: renderer
+ MouseArea {
anchors.fill: parent
- anchors.margins: 10
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: {
+ if (mouse.button === Qt.LeftButton) {
+ clipper.clip = !clipper.clip
+ } else if (mouse.button === Qt.RightButton) {
+ nonRectClipAnim.running = !nonRectClipAnim.running
+ if (!nonRectClipAnim.running)
+ clipper.rotation = 0;
+ }
+ }
+ }
+
+ Rectangle {
+ id: clipper
+ width: parent.width / 2
+ height: parent.height / 2
+ anchors.centerIn: parent
+ border.color: "yellow"
+ border.width: 2
+ color: "transparent"
+ NumberAnimation on rotation {
+ id: nonRectClipAnim
+ from: 0; to: 360; duration: 5000; loops: Animation.Infinite
+ running: false
+ }
+
+ CustomRenderItem {
+ id: renderer
+ width: bg.width - 20
+ height: bg.height - 20
+ x: -clipper.x + 10
+ y: -clipper.y + 10
- transform: [
- Rotation { id: rotation; axis.x: 0; axis.z: 0; axis.y: 1; angle: 0; origin.x: renderer.width / 2; origin.y: renderer.height / 2; },
- Translate { id: txOut; x: -renderer.width / 2; y: -renderer.height / 2 },
- Scale { id: scale; },
- Translate { id: txIn; x: renderer.width / 2; y: renderer.height / 2 }
- ]
+ transform: [
+ Rotation { id: rotation; axis.x: 0; axis.z: 0; axis.y: 1; angle: 0; origin.x: renderer.width / 2; origin.y: renderer.height / 2; },
+ Translate { id: txOut; x: -renderer.width / 2; y: -renderer.height / 2 },
+ Scale { id: scale; },
+ Translate { id: txIn; x: renderer.width / 2; y: renderer.height / 2 }
+ ]
+ }
}
SequentialAnimation {
@@ -94,17 +126,34 @@ Item {
Text {
id: label
- anchors.bottom: renderer.bottom
- anchors.left: renderer.left
- anchors.right: renderer.right
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
anchors.margins: 20
+ color: "yellow"
wrapMode: Text.WordWrap
property int api: GraphicsInfo.api
- text: "Custom rendering via the graphics API "
- + (api === GraphicsInfo.OpenGL ? "OpenGL"
- : api === GraphicsInfo.Direct3D12 ? "Direct3D 12"
- : api === GraphicsInfo.Software ? "Software" : "")
+ text: {
+ var apiStr;
+ switch (api) {
+ case GraphicsInfo.OpenGL: apiStr = "OpenGL (direct)"; break;
+ case GraphicsInfo.Direct3D12: apiStr = "Direct3D 12 (direct)"; break;
+ case GraphicsInfo.Software: apiStr = "Software (QPainter)"; break;
+ case GraphicsInfo.OpenGLRhi: apiStr = "OpenGL (RHI)"; break;
+ // the example has no other QSGRenderNode subclasses
+ default: apiStr = "<UNSUPPORTED>"; break;
+ }
+ "Custom rendering via the graphics API " + apiStr
+ + "\nLeft click to toggle clipping to yellow rect"
+ + "\nRight click to rotate (can be used to exercise stencil clip instead of scissor)"
+ }
+ }
+
+ Text {
+ id: label2
+ anchors.top: parent.top
+ anchors.right: parent.right
color: "yellow"
+ text: "Clip: " + (clipper.clip ? "ON" : "OFF") + " Rotation: " + (nonRectClipAnim.running ? "ON" : "OFF")
}
}
}
diff --git a/examples/quick/scenegraph/rendernode/openglrenderer.cpp b/examples/quick/scenegraph/rendernode/openglrenderer.cpp
index 3c68830db6..5e6b9e3656 100644
--- a/examples/quick/scenegraph/rendernode/openglrenderer.cpp
+++ b/examples/quick/scenegraph/rendernode/openglrenderer.cpp
@@ -148,17 +148,33 @@ void OpenGLRenderNode::render(const RenderState *state)
m_program->enableAttributeArray(0);
m_program->enableAttributeArray(1);
- // Note that clipping (scissor or stencil) is ignored in this example.
+ // We are prepared both for the legacy (direct OpenGL) and the modern
+ // (abstracted by RHI) OpenGL scenegraph. So set all the states that are
+ // important to us.
+
+ f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
f->glEnable(GL_BLEND);
f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ // Clip support.
+ if (state->scissorEnabled()) {
+ f->glEnable(GL_SCISSOR_TEST);
+ const QRect r = state->scissorRect(); // already bottom-up
+ f->glScissor(r.x(), r.y(), r.width(), r.height());
+ }
+ if (state->stencilEnabled()) {
+ f->glEnable(GL_STENCIL_TEST);
+ f->glStencilFunc(GL_EQUAL, state->stencilValue(), 0xFF);
+ f->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ }
+
f->glDrawArrays(GL_TRIANGLES, 0, 3);
}
QSGRenderNode::StateFlags OpenGLRenderNode::changedStates() const
{
- return BlendState;
+ return BlendState | ScissorState | StencilState;
}
QSGRenderNode::RenderingFlags OpenGLRenderNode::flags() const
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
index 3d8e3291b2..293cc4cb6b 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer.cpp
@@ -2665,7 +2665,8 @@ void Renderer::updateClipState(const QSGClipNode *clipList, Batch *batch) // RHI
{
// Note: No use of the clip-related speparate m_current* vars is allowed
// here. All stored in batch->clipState instead. To collect state during
- // renderBatches(), m_currentClipState is used.
+ // the prepare steps, m_currentClipState is used. It should not be used in
+ // the render steps afterwards.
// The stenciling logic is slightly different from the legacy GL path as we
// cannot just randomly clear the stencil buffer. We now put all clip
@@ -4089,7 +4090,7 @@ void Renderer::renderBatches()
if (b->merged)
ok = prepareRenderMergedBatch(b, &renderBatch);
else if (b->isRenderNode)
- ok = false; // ###
+ ok = prepareRhiRenderNode(b, &renderBatch);
else
ok = prepareRenderUnmergedBatch(b, &renderBatch);
if (ok)
@@ -4120,7 +4121,7 @@ void Renderer::renderBatches()
if (renderBatch->batch->merged)
renderMergedBatch(renderBatch);
else if (renderBatch->batch->isRenderNode)
- Q_UNREACHABLE(); // ###
+ renderRhiRenderNode(renderBatch->batch);
else
renderUnmergedBatch(renderBatch);
}
@@ -4359,7 +4360,7 @@ struct RenderNodeState : public QSGRenderNode::RenderState
bool m_stencilEnabled;
};
-void Renderer::renderRenderNode(Batch *batch)
+void Renderer::renderRenderNode(Batch *batch) // legacy (GL-only)
{
if (Q_UNLIKELY(debug_render()))
qDebug() << " -" << batch << "rendernode";
@@ -4475,6 +4476,105 @@ void Renderer::renderRenderNode(Batch *batch)
glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
}
+bool Renderer::prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch) // split prepare-render (RHI only)
+{
+ if (Q_UNLIKELY(debug_render()))
+ qDebug() << " -" << batch << "rendernode";
+
+ Q_ASSERT(batch->first->isRenderNode);
+ RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
+
+ setActiveRhiShader(nullptr, nullptr);
+
+ QSGNode *clip = e->renderNode->parent();
+ QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
+ rd->m_clip_list = nullptr;
+ while (clip != rootNode()) {
+ if (clip->type() == QSGNode::ClipNodeType) {
+ rd->m_clip_list = static_cast<QSGClipNode *>(clip);
+ break;
+ }
+ clip = clip->parent();
+ }
+
+ updateClipState(rd->m_clip_list, batch);
+
+ renderBatch->batch = batch;
+ renderBatch->sms = nullptr;
+
+ return true;
+}
+
+void Renderer::renderRhiRenderNode(const Batch *batch) // split prepare-render (RHI only)
+{
+ if (batch->clipState.type & ClipState::StencilClip)
+ enqueueStencilDraw(batch);
+
+ RenderNodeElement *e = static_cast<RenderNodeElement *>(batch->first);
+ QSGRenderNodePrivate *rd = QSGRenderNodePrivate::get(e->renderNode);
+
+ QMatrix4x4 pm = projectionMatrix();
+ if (m_useDepthBuffer) {
+ pm(2, 2) = m_zRange;
+ pm(2, 3) = 1.0f - e->order * m_zRange;
+ }
+
+ RenderNodeState state;
+ state.m_projectionMatrix = &pm;
+ const std::array<int, 4> scissor = batch->clipState.scissor.scissor();
+ state.m_scissorRect = QRect(scissor[0], scissor[1], scissor[2], scissor[3]);
+ state.m_stencilValue = batch->clipState.stencilRef;
+ state.m_scissorEnabled = batch->clipState.type & ClipState::ScissorClip;
+ state.m_stencilEnabled = batch->clipState.type & ClipState::StencilClip;
+
+ QSGNode *xform = e->renderNode->parent();
+ QMatrix4x4 matrix;
+ QSGNode *root = rootNode();
+ if (e->root) {
+ matrix = qsg_matrixForRoot(e->root);
+ root = e->root->sgNode;
+ }
+ while (xform != root) {
+ if (xform->type() == QSGNode::TransformNodeType) {
+ matrix = matrix * static_cast<QSGTransformNode *>(xform)->combinedMatrix();
+ break;
+ }
+ xform = xform->parent();
+ }
+ rd->m_matrix = &matrix;
+
+ QSGNode *opacity = e->renderNode->parent();
+ rd->m_opacity = 1.0;
+ while (opacity != rootNode()) {
+ if (opacity->type() == QSGNode::OpacityNodeType) {
+ rd->m_opacity = static_cast<QSGOpacityNode *>(opacity)->combinedOpacity();
+ break;
+ }
+ opacity = opacity->parent();
+ }
+
+ const QSGRenderNode::StateFlags changes = e->renderNode->changedStates();
+
+ QRhiCommandBuffer *cb = commandBuffer();
+ cb->beginExternal();
+ e->renderNode->render(&state);
+ cb->endExternal();
+
+ rd->m_matrix = nullptr;
+ rd->m_clip_list = nullptr;
+
+ if (changes & QSGRenderNode::ViewportState)
+ m_pstate.viewportSet = false;
+
+ if (changes & QSGRenderNode::ScissorState)
+ m_pstate.scissorSet = false;
+
+ // Do not bother with RenderTargetState. Where applicable, endExternal()
+ // ensures the correct target is rebound. For others (like Vulkan) it makes
+ // no sense since render() could not possibly do that on our command buffer
+ // which is in renderpass recording state.
+}
+
void Renderer::setCustomRenderMode(const QByteArray &mode)
{
if (mode.isEmpty())
diff --git a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
index ea9dab244f..4e374522d4 100644
--- a/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
+++ b/src/quick/scenegraph/coreapi/qsgbatchrenderer_p.h
@@ -817,6 +817,8 @@ private:
void enqueueStencilDraw(const Batch *batch);
const QMatrix4x4 &matrixForRoot(Node *node);
void renderRenderNode(Batch *batch);
+ bool prepareRhiRenderNode(Batch *batch, PreparedRenderBatch *renderBatch);
+ void renderRhiRenderNode(const Batch *batch);
void setActiveShader(QSGMaterialShader *program, ShaderManager::Shader *shader);
void setActiveRhiShader(QSGMaterialRhiShader *program, ShaderManager::Shader *shader);
diff --git a/src/quick/scenegraph/coreapi/qsgrendernode.cpp b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
index df3fa16a32..2892f2f966 100644
--- a/src/quick/scenegraph/coreapi/qsgrendernode.cpp
+++ b/src/quick/scenegraph/coreapi/qsgrendernode.cpp
@@ -81,8 +81,10 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
}
/*!
- This function should return a mask where each bit represents graphics states changed by
- the \l render() function:
+ When the underlying rendering API is OpenGL, this function should return a
+ mask where each bit represents graphics states changed by the \l render()
+ function:
+
\list
\li DepthState - depth write mask, depth test enabled, depth comparison function
\li StencilState - stencil write masks, stencil test enabled, stencil operations,
@@ -95,6 +97,29 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
\li RenderTargetState - render target
\endlist
+ With APIs other than OpenGL, the only relevant values are the ones that
+ correspond to dynamic state changes recorded on the command list/buffer.
+ For example, RSSetViewports, RSSetScissorRects, OMSetBlendFactor,
+ OMSetStencilRef in case of D3D12, or vkCmdSetViewport, vkCmdSetScissor,
+ vkCmdSetBlendConstants, vkCmdSetStencilRef in case of Vulkan, and only when
+ such commands were added to the scenegraph's command list queried via the
+ QSGRendererInterface::CommandList resource enum. States set in pipeline
+ state objects do not need to be reported here. Similarly, draw call related
+ settings (pipeline states, descriptor sets, vertex or index buffer
+ bindings, root signature, descriptor heaps, etc.) are always set again by
+ the scenegraph so render() can freely change them.
+
+ \note RenderTargetState is no longer supported with APIs like Vulkan. This
+ is by nature. render() is invoked while the Qt Quick scenegraph's main
+ command buffer is recording a renderpass, so there is no possibility of
+ changing the target and starting another renderpass (on that command buffer
+ at least). Therefore returning a value with RenderTargetState set is not
+ sensible.
+
+ The software backend exposes its QPainter and saves and restores before and
+ after invoking render(). Therefore reporting any changed states from here
+ is not necessary.
+
The function is called by the renderer so it can reset the states after
rendering this node. This makes the implementation of render() simpler
since it does not have to query and restore these states.
@@ -102,19 +127,6 @@ QSGRenderNodePrivate::QSGRenderNodePrivate()
The default implementation returns 0, meaning no relevant state was changed
in render().
- With APIs other than OpenGL the relevant states are only those that are set
- via the command list (for example, OMSetRenderTargets, RSSetViewports,
- RSSetScissorRects, OMSetBlendFactor, OMSetStencilRef in case of D3D12), and
- only when such commands were added to the scenegraph's command list queried
- via the QSGRendererInterface::CommandList resource enum. States set in
- pipeline state objects do not need to be reported here. Similarly, draw
- call related settings (root signature, descriptor heaps, etc.) are always
- set again by the scenegraph so render() can freely change them.
-
- The software backend exposes its QPainter and saves and restores before and
- after invoking render(). Therefore reporting any changed states from here
- is not necessary.
-
\note This function may be called before render().
*/
QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
@@ -149,18 +161,31 @@ QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
QQuickFramebufferObject, QQuickWindow::beforeRendering(), or the
equivalents of those for APIs other than OpenGL.
- Clip information is calculated before the function is called, it is however
- not enabled. Implementations wishing to take clipping into account can set
- up scissoring or stencil based on the information in \a state. Some
- scenegraph backends, software in particular, use no scissor or stencil.
- There the clip region is provided as an ordinary QRegion.
+ \note QSGRenderNode can perform significantly better than texture-based
+ approaches (such as, QQuickFramebufferObject), especially on systems where
+ the fragment processing power is limited. This is because it avoids
+ rendering to a texture and then drawing a textured quad. Rather,
+ QSGRenderNode allows recording draw calls in line with the scenegraph's
+ other commands, avoiding an additional render target and the potentially
+ expensive texturing and blending.
+
+ Clip information is calculated before the function is called.
+ Implementations wishing to take clipping into account can set up scissoring
+ or stencil based on the information in \a state. The stencil buffer is
+ filled with the necessary clip shapes, but it is up to the implementation
+ to enable stencil testing.
+
+ Some scenegraph backends, software in particular, use no scissor or
+ stencil. There the clip region is provided as an ordinary QRegion.
+
+ With the legacy, direct OpenGL based renderer, the following states are set
+ on the render thread's context before this function is called:
- For OpenGL the following states are set on the render thread's context
- before this function is called:
\list
+ \li glColorMask(true, true, true, true)
\li glDepthMask(false)
\li glDisable(GL_DEPTH_TEST)
- \li glStencilFunc(GL_EQUAL, state.stencilValue, 0xff) depending on clip
+ \li glStencilFunc(GL_EQUAL, state.stencilValue, 0xff); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP) depending on clip
\li glScissor(state.scissorRect.x(), state.scissorRect.y(),
state.scissorRect.width(), state.scissorRect.height()) depending on clip
\li glEnable(GL_BLEND)
@@ -168,23 +193,42 @@ QSGRenderNode::StateFlags QSGRenderNode::changedStates() const
\li glDisable(GL_CULL_FACE)
\endlist
- States that are not listed above, but are included in \l StateFlags, can
+ States that are not listed above, but are covered by \l StateFlags, can
have arbitrary values.
+ \note There is no state set with other graphics APIs, considering that many
+ of them do not have a concept of the traditional OpenGL state machine.
+ Rather, it is up to the implementation to create pipeline state objects
+ with the desired blending, scissor, and stencil tests enabled. Note that
+ this also includes OpenGL via the RHI. New QSGRenderNode implementations
+ are recommended to set all scissor, stencil and blend state explicitly (as
+ shown in the above list), even if they are targeting OpenGL.
+
\l changedStates() should return which states this function changes. If a
state is not covered by \l StateFlags, the state should be set to the
default value according to the OpenGL specification. For other APIs, see
the documentation for changedStates() for more information.
- \note Depth writes are disabled when this function is called (for example,
- glDepthMask(false) in case of OpenGL). Enabling depth writes can lead to
- unexpected results, depending on the scenegraph backend in use, so nodes
- should avoid this.
+ \note Depth writes are disabled when this function is called
+ (glDepthMask(false) with OpenGL). Enabling depth writes can lead to
+ unexpected results, depending on the scenegraph backend in use and the
+ content in the scene, so exercise caution with this.
For APIs other than OpenGL, it will likely be necessary to query certain
API-specific resources (for example, the graphics device or the command
list/buffer to add the commands to). This is done via QSGRendererInterface.
+ Assume nothing about the pipelines and dynamic states bound on the command
+ list/buffer when this function is called.
+
+ With some graphics APIs it can be necessary to also connect to the
+ QQuickWindow::beforeRendering() signal, because that is emitted before
+ recording the beginning of a renderpass on the command buffer
+ (vkCmdBeginRenderPass with Vulkan, or starting to encode via
+ MTLRenderCommandEncoder in case of Metal). Recording copy operations cannot
+ be done inside render() with such APIs. Rather, do it in the slot connected
+ (with DirectConnection) to the beforeRendering signal.
+
\sa QSGRendererInterface, QQuickWindow::rendererInterface()
*/
@@ -335,7 +379,8 @@ QSGRenderNode::RenderState::~RenderState()
/*!
\fn const QMatrix4x4 *QSGRenderNode::RenderState::scissorRect() const
- \return the current scissor rectangle when clipping is active.
+ \return the current scissor rectangle when clipping is active. x and y are
+ the bottom left coordinates.
\note Be aware of the differences between graphics APIs: for some the
scissor rect is only active when scissoring is enabled (for example,