From 4d68e18f06ffa12b5825d32f30a2ff1e2b5943d8 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 16 Oct 2023 08:13:00 +0200 Subject: Add rhiwindow example Task-number: QTBUG-113331 Change-Id: I4d775ecaeab5e8692a06d19b9951b139bc3d1051 Reviewed-by: Cristian Maureira-Fredes (cherry picked from commit ba4e29045017bdbbac43434bfcae124c8ccbc830) Reviewed-by: Qt Cherry-pick Bot --- examples/gui/rhiwindow/doc/rhiwindow.rst | 53 +++ examples/gui/rhiwindow/doc/rhiwindow.webp | Bin 0 -> 38466 bytes examples/gui/rhiwindow/main.py | 80 ++++ examples/gui/rhiwindow/rc_rhiwindow.py | 319 ++++++++++++++++ examples/gui/rhiwindow/rhiwindow.py | 420 +++++++++++++++++++++ examples/gui/rhiwindow/rhiwindow.pyproject | 5 + examples/gui/rhiwindow/rhiwindow.qrc | 8 + examples/gui/rhiwindow/shaders/color.frag | 15 + examples/gui/rhiwindow/shaders/color.vert | 17 + .../gui/rhiwindow/shaders/prebuilt/color.frag.qsb | Bin 0 -> 1035 bytes .../gui/rhiwindow/shaders/prebuilt/color.vert.qsb | Bin 0 -> 1131 bytes .../gui/rhiwindow/shaders/prebuilt/quad.frag.qsb | Bin 0 -> 1023 bytes .../gui/rhiwindow/shaders/prebuilt/quad.vert.qsb | Bin 0 -> 982 bytes examples/gui/rhiwindow/shaders/quad.frag | 11 + examples/gui/rhiwindow/shaders/quad.vert | 10 + 15 files changed, 938 insertions(+) create mode 100644 examples/gui/rhiwindow/doc/rhiwindow.rst create mode 100644 examples/gui/rhiwindow/doc/rhiwindow.webp create mode 100644 examples/gui/rhiwindow/main.py create mode 100644 examples/gui/rhiwindow/rc_rhiwindow.py create mode 100644 examples/gui/rhiwindow/rhiwindow.py create mode 100644 examples/gui/rhiwindow/rhiwindow.pyproject create mode 100644 examples/gui/rhiwindow/rhiwindow.qrc create mode 100644 examples/gui/rhiwindow/shaders/color.frag create mode 100644 examples/gui/rhiwindow/shaders/color.vert create mode 100644 examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb create mode 100644 examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb create mode 100644 examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb create mode 100644 examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb create mode 100644 examples/gui/rhiwindow/shaders/quad.frag create mode 100644 examples/gui/rhiwindow/shaders/quad.vert diff --git a/examples/gui/rhiwindow/doc/rhiwindow.rst b/examples/gui/rhiwindow/doc/rhiwindow.rst new file mode 100644 index 000000000..f38f9af63 --- /dev/null +++ b/examples/gui/rhiwindow/doc/rhiwindow.rst @@ -0,0 +1,53 @@ +RHI Window Example +================== + +This example shows how to create a minimal ``QWindow``-based +application using ``QRhi``. + +Qt 6.6 starts offering its accelerated 3D API and shader abstraction layer for +application use as well. Applications can now use the same 3D graphics classes +Qt itself uses to implement the ``Qt Quick`` scenegraph or the ``Qt Quick`` 3D +engine. In earlier Qt versions ``QRhi`` and the related classes were all +private APIs. From 6.6 on these classes are in a similar category as QPA family +of classes: neither fully public nor private, but something in-between, with a +more limited compatibility promise compared to public APIs. On the other hand, +``QRhi`` and the related classes now come with full documentation similarly to +public APIs. + +There are multiple ways to use ``QRhi``, the example here shows the most +low-level approach: targeting a ``QWindow``, while not using ``Qt Quick``, ``Qt +Quick 3D``, or Widgets in any form, and setting up all the rendering and +windowing infrastructure in the application. + +In contrast, when writing a QML application with ``Qt Quick`` or ``Qt Quick +3D``, and wanting to add ``QRhi``-based rendering to it, such an application is +going to rely on the window and rendering infrastructure ``Qt Quick`` has +already initialized, and it is likely going to query an existing ``QRhi`` +instance from the ``QQuickWindow``. There dealing with ``QRhi::create()``, +platform/API specifics or correctly handling ``QExposeEvent`` and resize events +for the window are all managed by Qt Quick. Whereas in this example, all that +is managed and taken care of by the application itself. + +.. note:: For ``QWidget``-based applications in particular, it should be noted + that ``QWidget::createWindowContainer()`` allows embedding a ``QWindow`` + (backed by a native window) into the widget-based user interface. Therefore, + the ``HelloWindow`` class from this example is reusable in ``QWidget``-based + applications, assuming the necessary initialization from ``main()`` is in place + as well. + + +Shaders +------- + +Due to being a Qt GUI/Python module example, this example cannot have a +dependency on the ``Qt Shader Tools`` module. This means that ``CMake`` helper +functions such as ``qt_add_shaders`` are not available for use. Therefore, the +example has the pre-processed ``.qsb`` files included in the +``shaders/prebuilt`` folder, and they are simply included within the executable +via a resource file}. This approach is not generally recommended for +applications. + + +.. image:: rhiwindow.webp + :width: 800 + :alt: RHI Window Example diff --git a/examples/gui/rhiwindow/doc/rhiwindow.webp b/examples/gui/rhiwindow/doc/rhiwindow.webp new file mode 100644 index 000000000..7ab3514af Binary files /dev/null and b/examples/gui/rhiwindow/doc/rhiwindow.webp differ diff --git a/examples/gui/rhiwindow/main.py b/examples/gui/rhiwindow/main.py new file mode 100644 index 000000000..261adf381 --- /dev/null +++ b/examples/gui/rhiwindow/main.py @@ -0,0 +1,80 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from argparse import ArgumentParser, RawDescriptionHelpFormatter +import sys + +from PySide6.QtCore import QCoreApplication +from PySide6.QtGui import QGuiApplication, QRhi, QSurfaceFormat + +from rhiwindow import HelloWindow +import rc_rhiwindow + +if __name__ == "__main__": + app = QGuiApplication(sys.argv) + + # Use platform-specific defaults when no command-line arguments given. + graphicsApi = QRhi.OpenGLES2 + if sys.platform == "win32": + graphicsApi = QRhi.D3D11 + elif sys.platform == "darwin": + graphicsApi = QRhi.Metal + + parser = ArgumentParser(formatter_class=RawDescriptionHelpFormatter, + description="QRhi render example") + parser.add_argument("--null", "-n", action="store_true", help="Null") + parser.add_argument("--opengl", "-g", action="store_true", help="OpenGL") + parser.add_argument("--d3d11", "-d", action="store_true", + help="Direct3D 11") + parser.add_argument("--d3d12", "-D", action="store_true", + help="Direct3D 12") + parser.add_argument("--metal", "-m", action="store_true", + help="Metal") + args = parser.parse_args() + if args.null: + graphicsApi = QRhi.Null + elif args.opengl: + graphicsApi = QRhi.OpenGLES2 + elif args.d3d11: + graphicsApi = QRhi.D3D11 + elif args.d3d12: + graphicsApi = QRhi.D3D12 + elif args.metal: + graphicsApi = QRhi.Metal + + # graphicsApi = QRhi.Vulkan? detect? needs QVulkanInstance + + # For OpenGL, to ensure there is a depth/stencil buffer for the window. + # With other APIs this is under the application's control + # (QRhiRenderBuffer etc.) and so no special setup is needed for those. + fmt = QSurfaceFormat() + fmt.setDepthBufferSize(24) + fmt.setStencilBufferSize(8) + # Special case macOS to allow using OpenGL there. + # (the default Metal is the recommended approach, though) + # gl_VertexID is a GLSL 130 feature, and so the default OpenGL 2.1 context + # we get on macOS is not sufficient. + if sys.platform == "darwin": + fmt.setVersion(4, 1) + fmt.setProfile(QSurfaceFormat.CoreProfile) + QSurfaceFormat.setDefaultFormat(fmt) + + window = HelloWindow(graphicsApi) + + window.resize(1280, 720) + title = QCoreApplication.applicationName() + " - " + window.graphicsApiName() + window.setTitle(title) + window.show() + + ret = app.exec() + + # RhiWindow::event() will not get invoked when the + # PlatformSurfaceAboutToBeDestroyed event is sent during the QWindow + # destruction. That happens only when exiting via app::quit() instead of + # the more common QWindow::close(). Take care of it: if the QPlatformWindow + # is still around (there was no close() yet), get rid of the swapchain + # while it's not too late. + if window.isVisible(): + window.releaseSwapChain() + + sys.exit(ret) diff --git a/examples/gui/rhiwindow/rc_rhiwindow.py b/examples/gui/rhiwindow/rc_rhiwindow.py new file mode 100644 index 000000000..67f6adeb9 --- /dev/null +++ b/examples/gui/rhiwindow/rc_rhiwindow.py @@ -0,0 +1,319 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.6.1 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x03\xd6\ +\x00\ +\x00\x0b\xa0x\x9c\xd5VMo\xdbF\x10]}8\x8e\ +\xe94i\x9a\xaf&q\xddM\xdd&\x94\xe3\xa8\xb4\xe0\ +\x04Ee\xb9-\x92\x221\x10\xc0n\x5c\x18\x05\x04\x83\ +P$\xca! \x93\x86H\x0a*\x02\x03\x05r\xcf\xb1\ +\xa7\xde\xf2\x0f\xdaC\xff@\x81\xfe\xa5^\x8avf\xf7\ +Q\xbb\xfapN\xb9d\x01\x8a\x9c\xb73\xf3f\xe6-\ +i\x0b!\x16\x84Y\x05\xba\xce\x8a\x81\xf0EF\xbfB\ +\x14\x19\xfc\x0fK\xbc\xc3\xc5D\xabt\x9d\xb7l^e\ +\xdc\xcf\xe0\xde1!\xa5\xdfJ\xc5\x95yvu\xa8\xc6\ +\xbb@/R\x8d\x1c\xbcH!|\x7f\xfct\xefi5\ +I;\xd5\x8d\xfb\x9eP\x04\xa5\x11\xc1\x05\x8a\xcbI\x8e\ +Za\xc4\xcf\xdc\xfd9\xba>\xe1X\xdaa{\x19\xb9\ +\xd9>\xa7r\xebb\x9f\x889q\x13{\x8b\xb8\xe7X\ +\x01X\xc1\xc2\x8a\xc0J*Wi\x84]\xa2_\xbe\xdf\ +\x22\xac\x04\xec*=q\xd3\x92\xaek\xc4;\x8f!\x14\ +\x15VV\x95\xb3/\xe3u\xd8\x0b\xc0\xae\x90\xed \xb6\ +\x00\xff\xbc\x16\x07\xfe\x8b\xe8\x93\xb1\xbb\xf0??a\x7f\ +\x08>\xe6\xbf\x06\xfe2\xf2\x7f\x8c\xfc\x02\xfel_G\ +\xfc\x12\xd97\xe0\x7f]\xcdO\xf7\x9f\xe7\xb8\x81\x9a\x96\ +P\xefM\xd4\xb4\x84\xb9\x97\xac\x1a\x96-\x8e3\x86\xf3\ +\xdb\xdc\xfeL\x99\xbf|\x93\xdb_\xc0\x9f\xf3W\x90\x8b\ +y\x1fP\x0dEa\x0e\x14\xe3\xff\x102G\xf7\x06\xb8\ +>\xc0L\xfe\x22\x94\xed\x0b\xc0x.\x7f\x03\xbb\x08\x9c\ +g\x13\x83\xf3#\xe0y\x9eK\xc8\x93\xc7\x5c\x06f\xc7\ +\x5c\x01\xbeK>\xf3Jo\x9d\x87\xf1-\xaan\x01X\ +\x03\xda\x7f\x0a}_\xc3\xff\x160i\xe5X\xc1<\xf8\ +z\x05\xecs\xf8\xf2\xde\x0f\x841\xf7m\xe0\xc2\xc2\xee\ +\x00+\xa8|\xf3jf.|\xef`\xae\x9c\xf7;\xf2\ +\xe7\xb9\xaeB\xabe\xd4\xbb\x0a\xff\x7f)\xc3W\xfa\xf5\ +-\xe3\x9db\xa3#\xcc;\xfd\xe7\xca \xe8'a\x1c\ +\xc9u\xcfs\x9cA\xab\xffs\x18\x1d\xcaA\xd0\xae\xc9\ +\x81\x9f\x0d\xea\x84\xc5aGr\xb8[q^:\x92\x16\ +o\xc8\x86rr\xbb\xbd\xb8\x95\xba\xeea\xcf\xdf\x0f\xfa\ +i0\xdc~$77\xe5zE\xde\x96\xb5\xca\x9a\xd4\ +\xdb\xf6.\xe3\x95\xba\xcaC\xf0n\x9c\x84)\xd3\xabt\ +\x1b\xae\xabr\xaf\xcaZ\xd5\xab\xc8{\x9ab\x9d\x9e\xd7\ +\xa4W\xf5\xd6$?\xd6\x9d\x13\xc7\x99hi(\xcc\xb2\ +Z\xaa\xbd\xc7-\xfdj\xb5\xf4\x87i\xe9>\xb5\x14g\ +\xe9{\xd5\x0e\xbf\xeb5\xd3M\xf1M\x92\xb6\xd2\xb0\xad\ +\xa97l\x92\xba\x83\xad0J\xa5\xa9)\xea\x04\xc3\xd1\ +\x96\x8a\x1a\xf5\x9e\xa4\xfd\xac\x9d\xca\xbd\xdd\xedg\xfb\xfe\ +\xc3~\x9c$\xfevt\x9c\xa5\x18D6\x9dH~-\ +\xf7\xf6G\xddR\xad\xb3\xb3\xecd\xa9IcqR\xf8\ +\x8f\xdf\xff\xf4pg\xe7\xd9#\xafn6\xc7\xda\xd0\x14\ +\xa6\xa9\x93\x5c$\x921\xf5g)\xa5\x09\xa6\xb5R\x05\ +\xbfM.\xe5p\xaab\xba4[\xb3.\x8bF\x22u\ +\xab\xc3\xa1\x92\xac\xab4\xebj\xd1\xa6\x07\xa0\xcf\xd5\xd4\ +x%\x89q\x18\xf8!?\xe7\xadL\x14\xd5`\x11]\ +\xcb\xaf:\xee\x80\x82\xad\x91h`F\x0d:I\xac\x0c\ +\xedd#\xd5\xf1\x9e\xc7\x8e\xd3\x94o\xfef\xa8\xd3\xc3\ +\xbb\xfd \xcd\xfa\xd1\x04\xc5\x89}|\xcb\xf8\x13\x82U\ +\xf8}%\x8c\xda\xbd\xac\x13\xc8\xcd\xa3 m\xf5|\xfa\ +\x97\xa6\x17>\xdfr\x0c\x9e\x84G\x9d/\xf9\xa7\xfab\ +\xcbq\xb2\x84?@Q\xeb(H\x8e[\xed@\xaa(\ +s\xea\x98\xc6c\xea\x19\x87\xad\xd9\xcc\x92\xa0\xef\xf6\xe2\ +v\xe4U\x0e\x0eN=p\xcd\xe61\x1e\xd9I\x9d7\ +5i\x93\x5c?\xb9\xb3^\x88fS;\xfba\xe7\xe0\ + W\xd3\x04\xf2\xd5\x90/O4yl\x868~h\ +Y\xed\x09\x85g\x9c\xdcY^\xd6\xf1\x8d\xa7\xe4\xcc\x8f\ +\xf0\x88w\xf4\xe9\x01\xfd\xd4\xc7\xc7R5\xd6b\xb2\x9a\ +s\xaa!|\x8d&\xbeHjM\xc8l\xf4>m\xef\ +\x7f\xf6^=\xc8\ +\x00\x00\x04\x0b\ +\x00\ +\x00\x0b\x95x\x9c\xd5Vmo\x1bE\x10\x1e\xbf$n\ +.%I\x9bR\x0a\x05\xb44\x12\xb2Ke.\xc5\xa5\ +\x88\x90((\x01Z\xa9RJS*$\xcb:]\xce\ +gg\xc1wg\xdd\xed\x99FU\xfe\x02\x1f\xf9)H\ +|\xe6\xaf\xc0\x7f\xe0\x0b\x82\x99\xdd9\xdf\xc6qi%\ +\xfa\xa5+\xado\xe7\xd9yyffo\xcf\x00\xb0\x04\ +\x00u\x9c\x15\x9c+0\x01\x0f\x02H`\x843E\xa4\ +\x86\x13\xfe\xe1\x01s\x06\x99]\x86\x01*\xfb0\x84=\ +\xcb\xb4\xfe2\xa6\x8bp\x049\x9a\xd3\xca\x83M\xb8\x8b\ +\xab}K\xa7\xaaw\x22\xe45\xc6\xd52\xa3\xbb\x96\xc6\ +Z\xb1X\xc1\xb8cd\x11\x80\x04\x05'\x1c`\xb7`\ +\xf2*\xc7\x22?\xfb\x16\xd1?j\xd5\x8d\x06\xc5t\xe0\ +\x02\xbc\xcf\xe8%L\xa0\xa2\x89/\xea\xe77\x0f\x0e\x1f\ +\xb43\xd5ow\xee\xb8\x86\xb3.\xb0\xde[\x85\x86f\ +J3\xf2eL85\xe7\xa2N\xb2\xa6\xf1\x06\xf9\xc0\ +\x15\xe1E\x0c\x92/Z\xf2=\x94Wy\xbd\xa0\xe5\x85\ +\xa9\xbc1\xd5)\xb1\x06\x98\x22\x16X\x85\xf5v\xb5\xef\ +\x9a\xc6\xaa\x1c\xe7\x12>oXqI\xfe\x80\xe5u\xd4\ +\xaaj\xb9\xa6\xb3\xa2\xf5U\x5cQ\xb1\x04\xce\xb7P\xbf\ +\xc1\xc5\xabk\xac\x8e\x952G\x8c\xf0-\x96\x97\x18#\ +}\x87\xf5k\xac\xbf\xcc\xfc\x1c\xd6_\xe6\xfa\x10v\x0d\ +\xe5\x15\xf6U\xd7\xf50uXa\x1fd\xbf\xc6\xbcV\ +\xd9~M\xf7\xc8`o\xa2|\x99\xb9\x92\xbf\x8fX^\ +g\x99\xec\xaf@q$\x01>\xc5zU\xa1<]\xc4\ +\xf1/D\xa8\xe6\xdb\xcc\xfd\x0d\xe6\xf7%\xa2Wt\x0c\ +\x13o\x9du\x16u\x8d\x0c\xfe3\xea8:ocw\ +\xd5\xb2\xbb6\xc7\xeem\xc6\xbfE\x1d\x92\xdfa[\xb0\ +\xb0\xeb\x8cU,\xec]\xc6\x88\xfbC\xac\x16\xd5\xeb=\ +\xb6\xbf\xce\xfb\xe4{\x073Z\xe2\xbd\xbf\xd1\xc3g\xda\ +\x0b\xd4\xf9l\x92\xd0\xe7'\x8d?7&a\x9a\xc9$\ +\x16\x9b\xae\xeb\x8c\xd30\x90Z:\x96\xc3\xe3\xb1\x18\x8c\ +\x12_m\x9d\x83e\x8c\xa0\x93\xa94\x0f\x948\xca\x07\ +\xce3G\xe0\x88|\xd5\x11\xd1d\xbc\xa5%m+\x92\ +\xb1\x1fHu\xb2\xe5\x9c\xa2E\x1e\xcbA\x92Fd\x22\ +\xbc\xcd\xbb\x88L\xfc\xf4D\xc6C1\x09\x83O\xc4\xc4\ +\x0b\x92Q\x92\x12\x9c\xc8\xbe \xc6\xcd\x16\xfb\x1e\x8e\xbc\ +\xafS\x7f\xb8\xef+\xbf\xeb\xf6\xc46Yt\x9al!\ +n\x92\xbb6\xc7\xbae\x0b-\x8c\xec83\x05x\x0a\ +\xe5\xf8\xad,\xc0m\xf7uN\xea\x17+\xa9\xdf\xcb\xa4\ +\xee\xbc\xa2\xa4\x92\x5cizb\x80\x8c\xf7LJ2~\ +q\x8eS\xf5\xff\x91\x1e\x9d\xf9\xdbev\xd5\xef\x02\xe4\ +5\x08SM\xefs\x91\x86C\x99\xa90m\x1e\xb9E\ +\xd44\xf9\xc9\x8b\xfc\x1f0\x8cN\xae\xf3\xb4C\x01<\ +L\x19\xf51\xca\x8f\xc9`\x90\x85\xaa\x19\xb8-\xbb\x06\ +\xa4\xc3$f\xf4:-S\x18\xac\xa5\xafd\xc0n\xed\ +b\xd8\x1bvI\xb8\xf8\x87\x0f\xef?z\xe2\xed\xa5I\ +\x96y\xf7\xe3q\xae\x8a\xfa\x9c\xd1\xc7\xa8\x8f\xbf\xfa~\ +\xef\xe0\xe0\xd1\xbek\x02\xce1?\xc8\xd5\x8c\xbdE\x04\ +=\x1c>\xf1\x1e\xfb\xe90T\xecB7\x85\x14\xbc\xe7\ +v\xc6x9\xdb\x1b\xcf\xee\x8dw\xa67\xe7\xd9\x98\xa6\ +\x9fKR`U\x86\xa1'i]\xc4-\x82l\xdb\x9b\ +\xedi\xc1\x0ajL\xd6\x00s\x02\x1a\xe3D\x0bF\xc9\ +F\xdag\xb2+\xbb\xa4\x0fG\xa8\xf24\x9eqpj\ +\x1f\xb8:\x7f\x00xT~\xdd\x90q0\xca\xfb\xa1\xf8\ +\x22\x0a\x95?\xf2\xf0\xcf\xc0H\x1e\xed8%\x9e\xc9\xa8\ +\xff1\xfd\xb4\x8fw\xf0\xfd\xc9\xe8\x02\x88\xfd(\xcc\xb0\ +n\xa1\xd0V\xf3\xee\xcc\xe9\xf1\xfc\xaf\xb7\x91\xad\x88\x9c\ +K\x84\x9f\xd7\xfanW\xd7\xb0\xe9\xb6z\xbd9\x962\ +\x9e\x7f\xe6\xba\xdd<\xc3\xb7g\x94\x04qiI^\xa3\ +0\xb6\xa2\x9aU\xb3\xf0\x85_\x01\xb4,Z\xd8\xeb\xdd\ +\x12A\x12\xa3\x18\xeb\xf4>\xa4C\x83\xfb\xe6E\xd5\x8c\ +Z\xd3\xcb\xa7pHs[<;5\x89'\xb3]3\ +gR\xc6\xed\x97\xba2\xac\xce&\xa6\xa1\xd4\xd1\x05\x1d\ +\x8d\xef\x90\x99{d\xfa\x11\x04\xfe\xaf;\xd3\xf6s\xfb\ +\x056\xab\xa7\xc7\xbf\xc3\x1a`\x81\ +\x00\x00\x03\xff\ +\x00\ +\x00\x0b\x1ax\x9c\xcdV[o\xdcD\x14\x9e\xbd\xa4K\ +\x9c\x96\x86\x96$\xa5\xe52M@r\xca\xca\x98U\x8a\ +\x10\x9b\x8d\x84\x12@\x95*\xa5\xedF-\xd2je\x19\ +\xefdc\xc9k\xaf\xec\xf1\x92R\xe5/\xf0\xc8\x0b\x8f\ +\xfc\x0c~\x03\xff\x87\x17\x04\xe7\xcc\x1c\xaf'\xdeM\x95\ +GF\x9a\xf5\x9co\xce\xe5;\x17;a\x8c\xad2\xc6\ +\x9a\xb0k\xb0\xdfa3\xe6\xb1\x1c~\x19\xab\xc3f\xff\ +\xd2bK\x16\x1a\xdca\xa7,e>\x1b\xb3C\x96\xb0\ +\x08vJ\xee\xdejZus\x83I&\xd89\x9c\xec\ +\xeb\x98\x5cw\xdd\xa0\xe7\xa8\x84\x1a\xedF}\xa7\x85Q\ +-\xc8\xf63B\xdf\x83l\x91\xc7\x1a\x98\xe0\xf3\x87\xa7\ +\xfd\xa7N&G\xce\xdec\x17\xef\xdfe\x8d9\xd7\xdb\ +\xac\xa5\x12\xc4=\xf1\xc3X\xdb3\xb6\x01{\x1d\xf4\x10\ +o\xa1\x0f8\xdd\x82\xe76\xc5(\xe4\x87\x86\x8cv\x1f\ +\x1b\xf2\x86!\xdf\x05Nu\xa5\xdfP\xd1\xf1\xbc\x09'\ +L\x8a\xc3\xde\x02\xfd\x16%\xd9TX\x132\xd2\xb1q\ +\x7f\x00\x9d\xb5\xe8\xbeV)L!?\x00\x7fk\xf0\xb4\ +\xc8\xfe&\xe1\x88uI\xbeE\x18\xc6\xbbM\xfe\xea\xa4\ +\xbf>\xaf\x89\xd6_\xa7Z\xd4\xe8\xfe}\xac8\xf1\xe9\ +\x92\xbcA\x18\xfa\xdb$\x7f\x0d\x857\x81\xb3\xce\x0d\xd7\ +\xe7$\xdf\xa7{\xf4\xf7\x80|\xa1\xcdWlE\xf1h\ +\x16\x9d\x85\xfd7 +\x14\x0bk\xb1J\xfa=\x90\xd7\ +T\x1fu>=\xca\xe5\x0e\xf1}\x05V-Us\xad\ +\x83\xf8\x01x\x5c%\xacG\xb5\xde\x22\x9f\xc7\xe0\x1d\xb9\ +\xdf#l\xcb\xa8+r\xfa\x16\xfc!\xd7\x0fI\xff>\ +\xf9@\xde\x1f\x11\xfe+\xe8lR\xbf\xef\x11^\xd8}\ +\xb2\xc4\x8e\x13\xfe\x1ctP~\xc8\xcaY)\xb0m\xc2\ +j\x06\xb6C\x18\xf2z\x06Y`\x1e\x9f\x92\xfd6\xdd\ +s\xcaw\x83\xee\xfe\x01\x0f_\xeb)i\xd2\x8c\xa30\ +b\xe5\xe8\xfc\xb53\x13i\x16&1\xff\xd2u\xadi\ +*\x82PIg\xe1\xf8l\xcaO\xa3\xc4\x97\xdd\x058\ +\x8c\x01\xb4\xf28\xd9\x85#\xf8\ +\xba\x82e\xd1\x86\xe1\xb0=\xff\xc8\x8c\xf4;\xa4_\xa1\ +\xc1\xa0xk\xd1w\xbb\xf8\x22\xe0]\x1fN\xc8\x98 \ +\xa5Pt\xb9\x8c\x8a\xbb\xc7\xdf\x5ct\x97\x8d\xa46\xb5\ +\x0bgm \xe5\x18\x13\x99T\xfby\xe5T\x1a\xedM\ +tW\xb1\xad+\x8a\x06}/*\xdf\x8c\x85\xffr*\ +\xbd_\xb8/\xb0\xaa\x9eZ\xff\x018\x196d\ +\x00\x00\x04k\ +\x00\ +\x00\x0d\xafx\x9c\xe5Wmo\x1bE\x10^\xfb\x9c\x94\ +\x5c\xc9K\x936i\xa1-\x9bZB6T\xc6\x09\x0e\ +B5\x89\x82ZD+UJ\xd5\xa0\x0a\xc9\x8aN\xe7\ +\xf39\x1c\xf8\xee\xac\xbb=\x93\xa8\xb2\xc4_\xe0\x0b?\ +\x8c\xdf\xc0\x8f\xe0\x0b\x82\x99\xddY\xdf\xde\xd9)\xaaT\ +\xf1\x85\x95\xd6\xdey\xd9y\x9e\x99\x9d\xdd8\x8c\xb1\x15\ +\xa6F\x15\xa6\xcd<\x16\xb3\x11\xcc\x04$\x0bf\xe5o\ +\x1al\xc1\xd8`cpMY\xc0\x04\xcc\x98E\xa0\xab\ +\xa1\xe1M\x9b*0\xd7\xd8\x849sX\xff\xbam\x99\ +\xf5Y\xc6\x86r\xe5\xb0}v\x00\xab'\x86OUZ\ +B\x08>\x86\xd5u\xd2\x1e\x17\x18\xd3X\x03\xdc1s\ +\x81\x03\x92\xbf$\x80c\x9d\xc0\xbb\x1a\x15\xfa\xd6A\x97\ +\xe9{\x90\xbbX\xbfX\xd5\xfa5t\xb5\xd9{\xacN\ +\xda\x1b\x90LE&\xb1,\xbf\xbf}~\xfa\xbc\x95\x8a\ +A\xabs\xd0V\xfc\xad\x19\xc0:\x9d!\x82\x84n\x80\ +\xa7 5X\x80-\x98\xf7q?XWh\xcdH\xbe\ +N2\xc6x\xca\x96\x00S\x0d]8\xad\xab\x90\xce\xf4\ +\xab\x92\xce\x92\xb1\xac\x99\xee)\xc4\xdd\xa1\xfdK\xe4\xaf\ +\xe5\xba\x11W\xeb qy(ZW!\xbfc\x8a\xbb\ +Cq\x91\xef\x1d\xf8~`\xf0Gy\xd7\x90\xef\x1b\xf9\ +m\xc1\xae\xaa\xb4[\x92#\xae\xb7a\x85\x07\xc0a\xee\ +\x80\xff5:\x10K\xeajP}\xb5F}\x97\xe4\x15\ +\xc3n\x13?m\xb7\x8d\xba`\xbcU\x8a\x87\xe7p\x0b\ +>\xd7\x08\x0b\xc7\xa7$\xaf\x93\xff]\x907\xc8\x7f]\ +\xf2Vu\xd516\x08s\x93\xf0o\x10\xe6&\x9d\xa9\ +E\x187\x09\xa3B\x187\xa5^\x8d\xdb oS\xcc\ +\x9a\xc4Pg\xb3M\x18\x18\xff6\xd5f\x87\xe2\xa3|\ +\x87th\xff`V;%\xdf#\xacU\xf2\xbf\xc7\xf2\ +\x1eB\xfb.qC\xfb\x17\x90S\x95\xe5\xcd\x8f\xfa?\ +A\x83}qH\xf5\x7f\x9fjx\x04\xd6\x15\x92\xbf\x06\ +\x0f\xc4\xfd\x90\xb8\xdc\x22\xffmY7\xa5?\xa4z\x7f\ +D\xf8\xbf\xc2\x9eU\xaa\xc5]\xd2c\x9c]\xea\x99-\ +\x8a\x838\x0f\xc8\xef/`\xfd\xa5\xba\xa25\xba7(\ +\x0cX~o\xff\xa8O\xfc$\x0d\xe2\x88\xef\xb5\xdb\xb6\ +\x9d\x8a$\xf3\x04\xefgC\xfb\xb5\xcda\x84\xae\xe8\xf0\ +p2\xeeJi8\x8a]\xc1\xe3\xb1\xeb\x05\xe2\xb2k\ +O\xbb\xb6\x9dE\xc10NB\xdc\xc2\x9d\xfd\x03\xd0L\ +\xdc\xe42\x88\xce\xf9\xc4\xf7>\xe7\x13\xc7\x8bGq\xd2\ +\xb5]!\x92\xa0\x9f\x09_\xe9\x17i;|\x1c\xa7\x81\ +\x002\x18%\x0e\x06\x1cI7\x9aD\x85B\xf1C\xbd\ +\x19\x95\xe7#\xe7\x05m\x02\x03\x10h\x01Y\xfe\x89\x11\ +ij\xdb\xa5\xfc/X>\x8c\xfc\xf7\xff/\xf9\xfff\ +\xe4\xff{\x9e\xff\xc1;\xca?\xceD)\xf7 *$\ +\xad\xc4\xff\x22[\xbc\x9c\xfby\xb2\xd6\xa6\x074\x87~\ +\x22\xd9>\xe2\x89\x7f\x1e\xa4\xc2O\x1a\xfd\xb6\x86M\xe2\ +\x9f\x9d\xd0\xfd\x11\x80e\xae\x9d\x8b\x0e\xc28\x08\xf3\x88\ +C\xde?\xc5\xc3a\xea\x8b\x86\xd7n\x9a%A\x1f*\ +K\xc9\xaf\xd3Tu\x82\xd2\xba\x22\xf0(\xac\x99I\xb7\ +`2\xcaVT/P\x16JHGw\xfa\xe2\xd9\xcb\ +W\xce\xe3$NS\xe7Y4\xce\x04%V\xda\x00,\ +\xbf\xfb\xe6\xfb\xc7''/\x9f\xb4\x8dD\x08\xc6\xb0\xee\ +)\xfa\x0b\xa2\x9fd\xa2\x14~\xc6\xfd\x8a\xe8\x85\xb4\xc1\ +\xe7\xf4\x95Q\x84\xa9n\x03hH\xe1\xbcm/\x84\xd9\ +\xa8\xa1s{\xa8\x8f\xac)\xfba\x9e\xb3\xea\xb4\xb9J\ +q\xa8\xed\xb9\xef\x04\xb8\xd6\xd0\x1a\xd80\xb5\x0c\x12\xe3\ +\x9c\x81\xe9\x91\x1f\x8b\xa4\x9f'\xa4\x14\x0b\x18\xa9\xdd\xb1\ +\x14\x94\x93\xa9i\x15\x93-4\xcf\x9co^\xadY'\ +\xc9\xce\xf6E\x96D%\xa0\xa9y[j\xf4\xe7\x89F\ +u\xa9\x1eD\xde(\x1b\xf8\xfc\xab\xd0\x17\xee\xc8\x81\x9f\ +g\xa3\xa0\x7fd\xe7\xfa4\x08\x07\x9f\xe1G\xeb\x87#\ +x\x0bR|\xf7\x227\xf4S\xb8\x02>\x97\xbb\xba\x0b\ +^\x95\xd9\xddz\xd3\xcbB\xbb\x90\x5c\x1b\x09/\xee\xb4\ +^/K\xe1\x06\x8fb/j7\xcf\xce\xae\xec\xb6^\ +O\x1f\x0a:\xcd\xc5\x0f\xa2+\xeeI\xaf7{\xa3\x1b\ +%\x00}YL\x97\xbd\xa6\x0e\x8f\xa7\xee_\xe4\xf4\xd5\ +\xaa\xa1\xe1x\x80\xb1u\xd3\x9c\x9d=\x84`\x11\x88\x91\ +\xac\xd3\xc7\xd8\xc2`W\xcf\x95\x04n\xce^d\x1d\x10\ +\xe7!\x7f=U\x94\xe2\xc2\xd1\x07\x91\xd9\xa6\xf1\x5c\x0b\ +\xe5o'x\x16\xdb\x95\x1a%V\xfd\x81\x0d\xb2$1\ +\xe9=-\xbd\xa9\xb3_\x12\x8c\xfe\xb1)u\xd1\x9c]\ +\xeb\xca~r\xfc\x03\x0d.\xdb\xc1\ +" + +qt_resource_name = b"\ +\x00\x0d\ +\x0d\xf9\xb2\xf2\ +\x00q\ +\x00u\x00a\x00d\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\ +\x00\x0e\ +\x04\x16\xeb\xb2\ +\x00c\ +\x00o\x00l\x00o\x00r\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\ +\x00\x0d\ +\x09\x18\xb0\xd2\ +\x00q\ +\x00u\x00a\x00d\x00.\x00f\x00r\x00a\x00g\x00.\x00q\x00s\x00b\ +\x00\x0e\ +\x00\xfb\xe9\x92\ +\x00c\ +\x00o\x00l\x00o\x00r\x00.\x00v\x00e\x00r\x00t\x00.\x00q\x00s\x00b\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00b\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xec\ +\x00\x00\x01\x8b8N2\x22\ +\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x03\xda\ +\x00\x00\x01\x8b8N2\x22\ +\x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x07\xe9\ +\x00\x00\x01\x8b8N2\x22\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x8b8N2\x22\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/examples/gui/rhiwindow/rhiwindow.py b/examples/gui/rhiwindow/rhiwindow.py new file mode 100644 index 000000000..dff56fec8 --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.py @@ -0,0 +1,420 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import numpy +import sys + +from PySide6.QtCore import (QEvent, QFile, QIODevice, QPointF, QRectF, QSize, + QSizeF, qFatal, qWarning, Qt) +from PySide6.QtGui import (QColor, QFont, QGradient, QImage, QMatrix4x4, + QOffscreenSurface, QPainter, QPlatformSurfaceEvent, + QSurface, QWindow) +from PySide6.QtGui import (QRhi, QRhiBuffer, QRhiCommandBuffer, + QRhiDepthStencilClearValue, + QRhiGraphicsPipeline, QRhiNullInitParams, + QRhiGles2InitParams, QRhiRenderBuffer, + QRhiSampler, QRhiShaderResourceBinding, + QRhiShaderStage, QRhiTexture, + QRhiVertexInputAttribute, QRhiVertexInputBinding, + QRhiVertexInputLayout, QRhiViewport, + QShader) +from PySide6.support import VoidPtr + +if sys.platform == "win32": + from PySide6.QtGui import QRhiD3D11InitParams, QRhiD3D12InitParams +elif sys.platform == "darwin": + from PySide6.QtGui import QRhiMetalInitParams + + +# Y up (note clipSpaceCorrMatrix in m_viewProjection), CCW +VERTEX_DATA = numpy.array([ + 0.0, 0.5, 1.0, 0.0, 0.0, + -0.5, -0.5, 0.0, 1.0, 0.0, + 0.5, -0.5, 0.0, 0.0, 1.0], dtype=numpy.float32) + + +UBUF_SIZE = 68 + + +def getShader(name): + f = QFile(name) + if f.open(QIODevice.ReadOnly): + result = QShader.fromSerialized(f.readAll()) + f.close() + return result + return QShader() + + +class RhiWindow(QWindow): + + def __init__(self, graphicsApi): + super().__init__() + self.m_graphicsApi = QRhi.Null + self.m_initialized = False + self.m_notExposed = False + self.m_newlyExposed = False + + self.m_fallbackSurface = None + self.m_rhi = None + self.m_sc = None + self.m_ds = None + self.m_rp = None + self.m_hasSwapChain = False + self.m_viewProjection = QMatrix4x4() + + self.m_graphicsApi = graphicsApi + + if graphicsApi == QRhi.OpenGLES2: + self.setSurfaceType(QSurface.SurfaceType.OpenGLSurface) + elif graphicsApi == QRhi.Vulkan: + self.setSurfaceType(QSurface.SurfaceType.VulkanSurface) + elif graphicsApi == QRhi.D3D11 or graphicsApi == QRhi.D3D12: + self.setSurfaceType(QSurface.SurfaceType.Direct3DSurface) + elif graphicsApi == QRhi.Metal: + self.setSurfaceType(QSurface.SurfaceType.MetalSurface) + elif graphicsApi == QRhi.Null: + pass # RasterSurface + + def __del__(self): + # destruction order matters to a certain degree: the fallbackSurface + # must outlive the rhi, the rhi must outlive all other resources. + # The resources need no special order when destroying. + del self.m_rp + self.m_rp = None + del self.m_ds + self.m_ds = None + del self.m_sc + self.m_sc = None + del self.m_rhi + self.m_rhi = None + if self.m_fallbackSurface: + del self.m_fallbackSurface + self.m_fallbackSurface = None + + def graphicsApiName(self): + if self.m_graphicsApi == QRhi.Null: + return "Null (no output)" + if self.m_graphicsApi == QRhi.OpenGLES2: + return "OpenGL" + if self.m_graphicsApi == QRhi.Vulkan: + return "Vulkan" + if self.m_graphicsApi == QRhi.D3D11: + return "Direct3D 11" + if self.m_graphicsApi == QRhi.D3D12: + return "Direct3D 12" + if self.m_graphicsApi == QRhi.Metal: + return "Metal" + return "" + + def customInit(self): + pass + + def customRender(self): + pass + + def exposeEvent(self, e): + # initialize and start rendering when the window becomes usable + # for graphics purposes + is_exposed = self.isExposed() + if is_exposed and not self.m_initialized: + self.init() + self.resizeSwapChain() + self.m_initialized = True + + surfaceSize = self.m_sc.surfacePixelSize() if self.m_hasSwapChain else QSize() + + # stop pushing frames when not exposed (or size is 0) + if (not is_exposed or (self.m_hasSwapChain and surfaceSize.isEmpty())) and self.m_initialized and not self.m_notExposed: + self.m_notExposed = True + + # Continue when exposed again and the surface has a valid size. Note + # that surfaceSize can be (0, 0) even though size() reports a valid + # one, hence trusting surfacePixelSize() and not QWindow. + if is_exposed and self.m_initialized and self.m_notExposed and not surfaceSize.isEmpty(): + self.m_notExposed = False + self.m_newlyExposed = True + + # always render a frame on exposeEvent() (when exposed) in order to + # update immediately on window resize. + if is_exposed and not surfaceSize.isEmpty(): + self.render() + + def event(self, e): + if e.type() == QEvent.UpdateRequest: + self.render() + elif e.type() == QEvent.PlatformSurface: + # this is the proper time to tear down the swapchain (while + # the native window and surface are still around) + if e.surfaceEventType() == QPlatformSurfaceEvent.SurfaceAboutToBeDestroyed: + self.releaseSwapChain() + + return super().event(e) + + def init(self): + if self.m_graphicsApi == QRhi.Null: + params = QRhiNullInitParams() + self.m_rhi = QRhi.create(QRhi.Null, params) + + if self.m_graphicsApi == QRhi.OpenGLES2: + self.m_fallbackSurface = QRhiGles2InitParams.newFallbackSurface() + params = QRhiGles2InitParams() + params.fallbackSurface = self.m_fallbackSurface + params.window = self + self.m_rhi = QRhi.create(QRhi.OpenGLES2, params) + elif self.m_graphicsApi == QRhi.D3D11: + params = QRhiD3D11InitParams() + # Enable the debug layer, if available. This is optional + # and should be avoided in production builds. + params.enableDebugLayer = True + self.m_rhi = QRhi.create(QRhi.D3D11, params) + elif self.m_graphicsApi == QRhi.D3D12: + params = QRhiD3D12InitParams() + # Enable the debug layer, if available. This is optional + # and should be avoided in production builds. + params.enableDebugLayer = True + self.m_rhi = QRhi.create(QRhi.D3D12, params) + elif self.m_graphicsApi == QRhi.Metal: + params = QRhiMetalInitParams() + self.m_rhi.reset(QRhi.create(QRhi.Metal, params)) + + if not self.m_rhi: + qFatal("Failed to create RHI backend") + + self.m_sc = self.m_rhi.newSwapChain() + # no need to set the size here, due to UsedWithSwapChainOnly + self.m_ds = self.m_rhi.newRenderBuffer(QRhiRenderBuffer.DepthStencil, + QSize(), 1, + QRhiRenderBuffer.UsedWithSwapChainOnly) + self.m_sc.setWindow(self) + self.m_sc.setDepthStencil(self.m_ds) + self.m_rp = self.m_sc.newCompatibleRenderPassDescriptor() + self.m_sc.setRenderPassDescriptor(self.m_rp) + self.customInit() + + def resizeSwapChain(self): + self.m_hasSwapChain = self.m_sc.createOrResize() # also handles self.m_ds + outputSize = self.m_sc.currentPixelSize() + 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 releaseSwapChain(self): + if self.m_hasSwapChain: + self.m_hasSwapChain = False + self.m_sc.destroy() + + def render(self): + if not self.m_hasSwapChain or self.m_notExposed: + return + + # If the window got resized or newly exposed, resize the swapchain. + # (the newly-exposed case is not actually required by some platforms, + # but is here for robustness and portability) + # + # This (exposeEvent + the logic here) is the only safe way to perform + # resize handling. Note the usage of the RHI's surfacePixelSize(), and + # never QWindow::size(). (the two may or may not be the same under the + # hood, # depending on the backend and platform) + if self.m_sc.currentPixelSize() != self.m_sc.surfacePixelSize() or self.m_newlyExposed: + self.resizeSwapChain() + if not self.m_hasSwapChain: + return + self.m_newlyExposed = False + + result = self.m_rhi.beginFrame(self.m_sc) + if result == QRhi.FrameOpSwapChainOutOfDate: + self.resizeSwapChain() + if not self.m_hasSwapChain: + return + result = self.m_rhi.beginFrame(self.m_sc) + + if result != QRhi.FrameOpSuccess: + qWarning(f"beginFrame failed with {result}, will retry") + self.requestUpdate() + return + + self.customRender() + + self.m_rhi.endFrame(self.m_sc) + + # Always request the next frame via requestUpdate(). On some platforms + # this is backed by a platform-specific solution, e.g. CVDisplayLink + # on macOS, which is potentially more efficient than a timer, + # queued metacalls, etc. + self.requestUpdate() + + +class HelloWindow(RhiWindow): + + def __init__(self, graphicsApi): + super().__init__(graphicsApi) + self.m_vbuf = None + self.m_ubuf = None + self.m_texture = None + self.m_sampler = None + self.m_colorTriSrb = None + self.m_colorPipeline = None + self.m_fullscreenQuadSrb = None + self.m_fullscreenQuadPipeline = None + self.m_initialUpdates = None + + self.m_rotation = 0 + self.m_opacity = 1 + self.m_opacityDir = -1 + + def ensureFullscreenTexture(self, pixelSize, u): + if self.m_texture and self.m_texture.pixelSize() == pixelSize: + return + + if not self.m_texture: + self.m_texture = self.m_rhi.newTexture(QRhiTexture.RGBA8, pixelSize) + else: + self.m_texture.setPixelSize(pixelSize) + self.m_texture.create() + image = QImage(pixelSize, QImage.Format_RGBA8888_Premultiplied) + with QPainter(image) as painter: + painter.fillRect(QRectF(QPointF(0, 0), pixelSize), + QColor.fromRgbF(0.4, 0.7, 0.0, 1.0)) + painter.setPen(Qt.transparent) + painter.setBrush(QGradient(QGradient.DeepBlue)) + painter.drawRoundedRect(QRectF(QPointF(20, 20), pixelSize - QSize(40, 40)), + 16, 16) + painter.setPen(Qt.black) + font = QFont() + font.setPixelSize(0.05 * min(pixelSize.width(), pixelSize.height())) + painter.setFont(font) + name = self.graphicsApiName() + t = (f"Rendering with QRhi to a resizable QWindow.\nThe 3D API is {name}." + "\nUse the command-line options to choose a different API.") + painter.drawText(QRectF(QPointF(60, 60), pixelSize - QSize(120, 120)), 0, t) + + if self.m_rhi.isYUpInNDC(): + image = image.mirrored() + + u.uploadTexture(self.m_texture, image) + + def customInit(self): + self.m_initialUpdates = self.m_rhi.nextResourceUpdateBatch() + + 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_initialUpdates.uploadStaticBuffer(self.m_vbuf, + VoidPtr(VERTEX_DATA.tobytes(), vertex_size)) + + self.m_ubuf = self.m_rhi.newBuffer(QRhiBuffer.Dynamic, + QRhiBuffer.UniformBuffer, UBUF_SIZE) + self.m_ubuf.create() + + self.ensureFullscreenTexture(self.m_sc.surfacePixelSize(), self.m_initialUpdates) + + self.m_sampler = self.m_rhi.newSampler(QRhiSampler.Linear, QRhiSampler.Linear, + QRhiSampler.None_, + QRhiSampler.ClampToEdge, QRhiSampler.ClampToEdge) + self.m_sampler.create() + + self.m_colorTriSrb = self.m_rhi.newShaderResourceBindings() + visibility = (QRhiShaderResourceBinding.VertexStage + | QRhiShaderResourceBinding.FragmentStage) + bindings = [ + QRhiShaderResourceBinding.uniformBuffer(0, visibility, self.m_ubuf) + ] + self.m_colorTriSrb.setBindings(bindings) + self.m_colorTriSrb.create() + + self.m_colorPipeline = self.m_rhi.newGraphicsPipeline() + # Enable depth testing; not quite needed for a simple triangle, but we + # have a depth-stencil buffer so why not. + self.m_colorPipeline.setDepthTest(True) + self.m_colorPipeline.setDepthWrite(True) + # Blend factors default to One, OneOneMinusSrcAlpha, which is convenient. + premulAlphaBlend = QRhiGraphicsPipeline.TargetBlend() + premulAlphaBlend.enable = True + self.m_colorPipeline.setTargetBlends([premulAlphaBlend]) + stages = [ + QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/color.vert.qsb")), + QRhiShaderStage(QRhiShaderStage.Fragment, getShader(":/color.frag.qsb")) + ] + self.m_colorPipeline.setShaderStages(stages) + inputLayout = QRhiVertexInputLayout() + input_bindings = [QRhiVertexInputBinding(5 * 4)] # sizeof(float) + inputLayout.setBindings(input_bindings) + attributes = [ + QRhiVertexInputAttribute(0, 0, QRhiVertexInputAttribute.Float2, 0), + QRhiVertexInputAttribute(0, 1, QRhiVertexInputAttribute.Float3, 2 * 4)] # sizeof(float) + inputLayout.setAttributes(attributes) + self.m_colorPipeline.setVertexInputLayout(inputLayout) + self.m_colorPipeline.setShaderResourceBindings(self.m_colorTriSrb) + self.m_colorPipeline.setRenderPassDescriptor(self.m_rp) + self.m_colorPipeline.create() + + self.m_fullscreenQuadSrb = self.m_rhi.newShaderResourceBindings() + bindings = [ + QRhiShaderResourceBinding.sampledTexture(0, QRhiShaderResourceBinding.FragmentStage, + self.m_texture, self.m_sampler) + ] + self.m_fullscreenQuadSrb.setBindings(bindings) + self.m_fullscreenQuadSrb.create() + + self.m_fullscreenQuadPipeline = self.m_rhi.newGraphicsPipeline() + stages = [ + QRhiShaderStage(QRhiShaderStage.Vertex, getShader(":/quad.vert.qsb")), + QRhiShaderStage(QRhiShaderStage.Fragment, getShader(":/quad.frag.qsb")) + ] + self.m_fullscreenQuadPipeline.setShaderStages(stages) + layout = QRhiVertexInputLayout() + self.m_fullscreenQuadPipeline.setVertexInputLayout(layout) + self.m_fullscreenQuadPipeline.setShaderResourceBindings(self.m_fullscreenQuadSrb) + self.m_fullscreenQuadPipeline.setRenderPassDescriptor(self.m_rp) + self.m_fullscreenQuadPipeline.create() + + def customRender(self): + resourceUpdates = self.m_rhi.nextResourceUpdateBatch() + + if self.m_initialUpdates: + resourceUpdates.merge(self.m_initialUpdates) + self.m_initialUpdates = None + + 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()) + + self.m_opacity += self.m_opacityDir * 0.005 + if self.m_opacity < 0.0 or self.m_opacity > 1.0: + self.m_opacityDir *= -1 + self.m_opacity = max(0.0, min(1.0, self.m_opacity)) + + opacity = numpy.array([self.m_opacity], dtype=numpy.float32) + resourceUpdates.updateDynamicBuffer(self.m_ubuf, 64, 4, + opacity.tobytes()) + + cb = self.m_sc.currentFrameCommandBuffer() + outputSizeInPixels = self.m_sc.currentPixelSize() + + # (re)create the texture with a size matching the output surface size, + # when necessary. + self.ensureFullscreenTexture(outputSizeInPixels, resourceUpdates) + + cv = QRhiDepthStencilClearValue(1.0, 0) + cb.beginPass(self.m_sc.currentFrameRenderTarget(), Qt.black, + cv, resourceUpdates) + + cb.setGraphicsPipeline(self.m_fullscreenQuadPipeline) + viewport = QRhiViewport(0, 0, outputSizeInPixels.width(), + outputSizeInPixels.height()) + cb.setViewport(viewport) + cb.setShaderResources() + cb.draw(3) + + cb.setGraphicsPipeline(self.m_colorPipeline) + cb.setShaderResources() + vbufBinding = (self.m_vbuf, 0) + cb.setVertexInput(0, [vbufBinding]) + cb.draw(3) + cb.endPass() diff --git a/examples/gui/rhiwindow/rhiwindow.pyproject b/examples/gui/rhiwindow/rhiwindow.pyproject new file mode 100644 index 000000000..a807b49bf --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.pyproject @@ -0,0 +1,5 @@ +{ + "files": ["main.py", "rhiwindow.py", "rhiwindow.qrc", + "shaders/color.frag", "shaders/color.vert", + "shaders/quad.frag", "shaders/quad.vert"] +} diff --git a/examples/gui/rhiwindow/rhiwindow.qrc b/examples/gui/rhiwindow/rhiwindow.qrc new file mode 100644 index 000000000..1009ec5dd --- /dev/null +++ b/examples/gui/rhiwindow/rhiwindow.qrc @@ -0,0 +1,8 @@ + + + shaders/prebuilt/color.vert.qsb + shaders/prebuilt/color.frag.qsb + shaders/prebuilt/quad.vert.qsb + shaders/prebuilt/quad.frag.qsb + + diff --git a/examples/gui/rhiwindow/shaders/color.frag b/examples/gui/rhiwindow/shaders/color.frag new file mode 100644 index 000000000..6e0a3bc91 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/color.frag @@ -0,0 +1,15 @@ +#version 440 + +layout(location = 0) in vec3 v_color; + +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 mvp; + float opacity; +}; + +void main() +{ + fragColor = vec4(v_color * opacity, opacity); +} diff --git a/examples/gui/rhiwindow/shaders/color.vert b/examples/gui/rhiwindow/shaders/color.vert new file mode 100644 index 000000000..70852ab86 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/color.vert @@ -0,0 +1,17 @@ +#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; + float opacity; +}; + +void main() +{ + v_color = color; + gl_Position = mvp * position; +} diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb new file mode 100644 index 000000000..b4db470e5 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/color.frag.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb new file mode 100644 index 000000000..ab046b77f Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/color.vert.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb new file mode 100644 index 000000000..c2ea3cf25 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/quad.frag.qsb differ diff --git a/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb new file mode 100644 index 000000000..f0b64f750 Binary files /dev/null and b/examples/gui/rhiwindow/shaders/prebuilt/quad.vert.qsb differ diff --git a/examples/gui/rhiwindow/shaders/quad.frag b/examples/gui/rhiwindow/shaders/quad.frag new file mode 100644 index 000000000..65882a429 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/quad.frag @@ -0,0 +1,11 @@ +#version 440 + +layout(location = 0) in vec2 v_uv; +layout(location = 0) out vec4 fragColor; +layout(binding = 0) uniform sampler2D tex; + +void main() +{ + vec4 c = texture(tex, v_uv); + fragColor = vec4(c.rgb * c.a, c.a); +} diff --git a/examples/gui/rhiwindow/shaders/quad.vert b/examples/gui/rhiwindow/shaders/quad.vert new file mode 100644 index 000000000..359896d08 --- /dev/null +++ b/examples/gui/rhiwindow/shaders/quad.vert @@ -0,0 +1,10 @@ +#version 440 + +layout (location = 0) out vec2 v_uv; + +void main() +{ + // https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/ + v_uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(v_uv * 2.0 - 1.0, 0.0, 1.0); +} -- cgit v1.2.3