简单的RHI小部件示例¶
展示如何使用QRhi
,Qt的3D API和着色语言抽象层来渲染一个三角形。
这个例子在很多方面是RHI窗口示例在QWidget世界中的对应物。这个应用程序中的QRhiWidget
子类使用一个简单的图形管道和基本的顶点和片段着色器渲染一个三角形。与基于QWindow
的普通应用程序不同,这个示例不需要担心较低级别的细节,例如设置窗口和QRhi
,或处理交换链和窗口事件,因为这里由QWidget
框架负责。QRhiWidget
子类的实例被添加到一个QVBoxLayout
中。为了保持示例的简洁和紧凑,没有引入更多的部件或3D内容。
一旦将ExampleRhiWidget
的实例,即QRhiWidget
的子类,添加到顶级窗口的子层次结构中,相应的窗口将自动成为Direct 3D、Vulkan、Metal或OpenGL渲染的窗口。由QPainter
渲染的窗口内容,即所有不是QRhiWidget
、QOpenGLWidget
或QQuickWidget
的内容,将被上传到纹理中,而上述特殊窗口各自渲染到纹理中。最终生成的纹理集由顶级窗口的backingstore合成在一起。
与C++示例不同,清理工作是通过重新实现QRhiWidget.releaseResources()
来完成的,该函数从顶级窗口的QWidget.closeEvent()调用,以确保确定的清理顺序。

# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
"""PySide6 port of the Qt Simple RHI Widget Example example from Qt v6.x"""
import sys
from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget
from examplewidget import ExampleRhiWidget
import rc_simplerhiwidget # noqa F:401
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
self._rhi_widget = ExampleRhiWidget(self)
layout.addWidget(self._rhi_widget)
def closeEvent(self, e):
self._rhi_widget.releaseResources()
e.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Widget()
w.resize(1280, 720)
w.show()
exit_code = app.exec()
del w
sys.exit(exit_code)
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import numpy
from PySide6.QtCore import (QFile, QIODevice)
from PySide6.QtGui import (QColor, QMatrix4x4)
from PySide6.QtGui import (QRhiBuffer,
QRhiDepthStencilClearValue,
QRhiShaderResourceBinding,
QRhiShaderStage,
QRhiVertexInputAttribute, QRhiVertexInputBinding,
QRhiVertexInputLayout, QRhiViewport,
QShader)
from PySide6.QtWidgets import QRhiWidget
from PySide6.support import VoidPtr
VERTEX_DATA = numpy.array([ 0.0, 0.5, 1.0, 0.0, 0.0, # noqa E:201
-0.5, -0.5, 0.0, 1.0, 0.0, # noqa E:241
0.5, -0.5, 0.0, 0.0, 1.0],
dtype=numpy.float32)
def getShader(name):
f = QFile(name)
if f.open(QIODevice.ReadOnly):
return QShader.fromSerialized(f.readAll())
return QShader()
class ExampleRhiWidget(QRhiWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.m_rhi = None
self.m_vbuf = None
self.m_ubuf = None
self.m_srb = None
self.m_pipeline = None
self.m_viewProjection = QMatrix4x4()
self.m_rotation = 0.0
def releaseResources(self):
self.m_pipeline.destroy()
del self.m_pipeline
self.m_pipeline = None
self.m_srb.destroy()
del self.m_srb
self.m_srb = None
self.m_ubuf.destroy()
del self.m_ubuf
self.m_ubuf = None
self.m_vbuf.destroy()
del self.m_vbuf
self.m_buf = None
def initialize(self, cb):
if self.m_rhi != self.rhi():
self.m_pipeline = None
self.m_rhi = self.rhi()
if not self.m_pipeline:
vertex_size = 4 * VERTEX_DATA.size
self.m_vbuf = self.m_rhi.newBuffer(QRhiBuffer.Immutable,
QRhiBuffer.VertexBuffer, vertex_size)
self.m_vbuf.create()
self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic,
QRhiBuffer.UniformBuffer, 64)
self.m_ubuf.create()
self.m_srb = self.m_rhi.newShaderResourceBindings()
bindings = [
QRhiShaderResourceBinding.uniformBuffer(0, QRhiShaderResourceBinding.VertexStage,
self.m_ubuf)
]
self.m_srb.setBindings(bindings)
self.m_srb.create()
self.m_pipeline = self.m_rhi.newGraphicsPipeline()
stages = [
QRhiShaderStage(QRhiShaderStage.Vertex,
getShader(":/shader_assets/color.vert.qsb")),
QRhiShaderStage(QRhiShaderStage.Fragment,
getShader(":/shader_assets/color.frag.qsb"))
]
self.m_pipeline.setShaderStages(stages)
inputLayout = QRhiVertexInputLayout()
input_bindings = [QRhiVertexInputBinding(5 * 4)] # sizeof(float)
inputLayout.setBindings(input_bindings)
attributes = [ # 4: sizeof(float)
QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Float2, 0),
QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Float3, 2 * 4)
]
inputLayout.setAttributes(attributes)
self.m_pipeline.setVertexInputLayout(inputLayout)
self.m_pipeline.setShaderResourceBindings(self.m_srb)
self.m_pipeline.setRenderPassDescriptor(self.renderTarget().renderPassDescriptor())
self.m_pipeline.create()
resourceUpdates = self.m_rhi.nextResourceUpdateBatch()
resourceUpdates.uploadStaticBuffer(self.m_vbuf, VoidPtr(VERTEX_DATA.tobytes(),
vertex_size))
cb.resourceUpdate(resourceUpdates)
outputSize = self.renderTarget().pixelSize()
self.m_viewProjection = self.m_rhi.clipSpaceCorrMatrix()
r = float(outputSize.width()) / float(outputSize.height())
self.m_viewProjection.perspective(45.0, r, 0.01, 1000.0)
self.m_viewProjection.translate(0, 0, -4)
def render(self, cb):
resourceUpdates = self.m_rhi.nextResourceUpdateBatch()
self.m_rotation += 1.0
modelViewProjection = self.m_viewProjection
modelViewProjection.rotate(self.m_rotation, 0, 1, 0)
projection = numpy.array(modelViewProjection.data(),
dtype=numpy.float32)
resourceUpdates.updateDynamicBuffer(self.m_ubuf, 0, 64,
projection.tobytes())
clearColor = QColor.fromRgbF(0.4, 0.7, 0.0, 1.0)
cv = QRhiDepthStencilClearValue(1.0, 0)
cb.beginPass(self.renderTarget(), clearColor, cv, resourceUpdates)
cb.setGraphicsPipeline(self.m_pipeline)
outputSize = self.renderTarget().pixelSize()
cb.setViewport(QRhiViewport(0, 0, outputSize.width(),
outputSize.height()))
cb.setShaderResources()
vbufBinding = (self.m_vbuf, 0)
cb.setVertexInput(0, [vbufBinding])
cb.draw(3)
cb.endPass()
self.update()
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>shader_assets/color.vert.qsb</file>
<file>shader_assets/color.frag.qsb</file>
</qresource>
</RCC>
#version 440
layout(location = 0) in vec3 v_color;
layout(location = 0) out vec4 fragColor;
void main()
{
fragColor = vec4(v_color, 1.0);
}
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec3 color;
layout(location = 0) out vec3 v_color;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
};
void main()
{
v_color = color;
gl_Position = mvp * position;
}