简单RHI小部件示例#
演示如何使用Qt的3D API和着色语言抽象层QRhi渲染三角形。
这个示例在许多方面是QWidget世界中的RHI窗口示例的对立面。此应用程序中的QRhiWidget子类通过使用简单的图形管线和基本的顶点着色器和片段着色器渲染单个三角形。与基于简单QWindow的应用程序不同,此示例无需关注底层详细信息,例如窗口设置和QRhi,或者处理交换链和窗口事件,因为QWidget框架已经处理了这些方面。QRhiWidget子类的实例被添加到QVBoxLayout中。为了使示例简单紧凑,没有引入其他小部件或3D内容。
一旦将一个 ExampleRhiWidget
实例(它是 QRhiWidget
的一个子类)添加到顶层小部件的子层次结构中,相应的窗口将自动成为一个 Direct3D、Vulkan、Metal 或 OpenGL 渲染的窗口。随后,由 QPainter
渲染的小部件内容(即不是 QRhiWidget
、QOpenGLWidget
或 QQuickWidget
的所有内容)将被上传到一个纹理中,而上述提到的特殊小部件各自渲染到一个纹理。顶层小部件的后备存储将组合生成的所有纹理。
与 C++ 示例相反,清理操作通过重新实现 QRhiWidget.releaseResources()
完成,该函数从顶层小部件的 QWidget.closeEvent() 中调用,以确保有确定的清理顺序。
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""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
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;
}