summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/.prev_CMakeLists.txt56
-rw-r--r--src/gui/CMakeLists.txt52
-rw-r--r--src/gui/accessible/qaccessible.cpp1
-rw-r--r--src/gui/accessible/qaccessible.h1
-rw-r--r--src/gui/configure.cmake6
-rw-r--r--src/gui/configure.json7
-rw-r--r--src/gui/doc/qtgui.qdocconf1
-rw-r--r--src/gui/doc/snippets/code/doc_src_coordsys.cpp2
-rw-r--r--src/gui/doc/snippets/textblock-fragments/mainwindow.cpp2
-rw-r--r--src/gui/doc/snippets/textdocument-blocks/mainwindow.cpp2
-rw-r--r--src/gui/doc/src/coordsys.qdoc9
-rw-r--r--src/gui/doc/src/dontdocument.qdoc66
-rw-r--r--src/gui/gui.pro1
-rw-r--r--src/gui/image/image.pri3
-rw-r--r--src/gui/image/qicon.cpp4
-rw-r--r--src/gui/image/qimage.cpp8
-rw-r--r--src/gui/image/qimage.h9
-rw-r--r--src/gui/image/qimage_compat.cpp66
-rw-r--r--src/gui/image/qimagereader.cpp10
-rw-r--r--src/gui/image/qmovie.cpp5
-rw-r--r--src/gui/image/qpicture.cpp2
-rw-r--r--src/gui/image/qpixmap.cpp10
-rw-r--r--src/gui/image/qpnghandler.cpp2
-rw-r--r--src/gui/image/qxpmhandler.cpp30
-rw-r--r--src/gui/kernel/qevent.cpp62
-rw-r--r--src/gui/kernel/qevent_p.h2
-rw-r--r--src/gui/kernel/qguiapplication.cpp7
-rw-r--r--src/gui/kernel/qguiapplication_p.h4
-rw-r--r--src/gui/kernel/qhighdpiscaling.cpp52
-rw-r--r--src/gui/kernel/qhighdpiscaling_p.h295
-rw-r--r--src/gui/kernel/qkeysequence.cpp2
-rw-r--r--src/gui/kernel/qopenglcontext.cpp5
-rw-r--r--src/gui/kernel/qopenglcontext_p.h5
-rw-r--r--src/gui/kernel/qopenglwindow.cpp2
-rw-r--r--src/gui/kernel/qpalette.cpp7
-rw-r--r--src/gui/kernel/qsimpledrag.cpp14
-rw-r--r--src/gui/kernel/qsurfaceformat.cpp2
-rw-r--r--src/gui/kernel/qwindow.cpp6
-rw-r--r--src/gui/kernel/qwindowsysteminterface.cpp4
-rw-r--r--src/gui/math3d/qvector3d.cpp2
-rw-r--r--src/gui/opengl/qopenglframebufferobject.cpp2
-rw-r--r--src/gui/opengl/qopenglfunctions_1_0.cpp4
-rw-r--r--src/gui/opengl/qopenglfunctions_1_1.cpp8
-rw-r--r--src/gui/opengl/qopenglfunctions_1_2.cpp12
-rw-r--r--src/gui/opengl/qopenglfunctions_1_3.cpp16
-rw-r--r--src/gui/opengl/qopenglfunctions_1_4.cpp20
-rw-r--r--src/gui/opengl/qopenglfunctions_1_5.cpp22
-rw-r--r--src/gui/opengl/qopenglfunctions_2_0.cpp24
-rw-r--r--src/gui/opengl/qopenglfunctions_2_1.cpp26
-rw-r--r--src/gui/opengl/qopenglfunctions_3_0.cpp28
-rw-r--r--src/gui/opengl/qopenglfunctions_3_1.cpp20
-rw-r--r--src/gui/opengl/qopenglfunctions_3_2_compatibility.cpp32
-rw-r--r--src/gui/opengl/qopenglfunctions_3_2_core.cpp22
-rw-r--r--src/gui/opengl/qopenglfunctions_3_3_compatibility.cpp36
-rw-r--r--src/gui/opengl/qopenglfunctions_3_3_core.cpp24
-rw-r--r--src/gui/opengl/qopenglfunctions_4_0_compatibility.cpp38
-rw-r--r--src/gui/opengl/qopenglfunctions_4_0_core.cpp26
-rw-r--r--src/gui/opengl/qopenglfunctions_4_1_compatibility.cpp40
-rw-r--r--src/gui/opengl/qopenglfunctions_4_1_core.cpp28
-rw-r--r--src/gui/opengl/qopenglfunctions_4_2_compatibility.cpp42
-rw-r--r--src/gui/opengl/qopenglfunctions_4_2_core.cpp30
-rw-r--r--src/gui/opengl/qopenglfunctions_4_3_compatibility.cpp44
-rw-r--r--src/gui/opengl/qopenglfunctions_4_3_core.cpp32
-rw-r--r--src/gui/opengl/qopenglfunctions_4_4_compatibility.cpp46
-rw-r--r--src/gui/opengl/qopenglfunctions_4_4_core.cpp34
-rw-r--r--src/gui/opengl/qopenglfunctions_4_5_compatibility.cpp50
-rw-r--r--src/gui/opengl/qopenglfunctions_4_5_core.cpp36
-rw-r--r--src/gui/opengl/qopenglpaintengine.cpp5
-rw-r--r--src/gui/opengl/qopengltextureblitter.cpp3
-rw-r--r--src/gui/painting/qbrush.cpp8
-rw-r--r--src/gui/painting/qbrush.h4
-rw-r--r--src/gui/painting/qcolor.cpp7
-rw-r--r--src/gui/painting/qcolor.h42
-rw-r--r--src/gui/painting/qcolorspace_p.h2
-rw-r--r--src/gui/painting/qcolortransform.cpp4
-rw-r--r--src/gui/painting/qdrawhelper.cpp37
-rw-r--r--src/gui/painting/qpaintengine_raster.cpp56
-rw-r--r--src/gui/painting/qpainter.cpp28
-rw-r--r--src/gui/painting/qpainter.h6
-rw-r--r--src/gui/painting/qpainter_p.h1
-rw-r--r--src/gui/painting/qpainterpath.cpp62
-rw-r--r--src/gui/painting/qpainterpath_p.h4
-rw-r--r--src/gui/painting/qpen.cpp6
-rw-r--r--src/gui/painting/qstroker.cpp2
-rw-r--r--src/gui/painting/qtextureglyphcache.cpp8
-rw-r--r--src/gui/rhi/qrhi.cpp5334
-rw-r--r--src/gui/rhi/qrhi_p.h1434
-rw-r--r--src/gui/rhi/qrhi_p_p.h566
-rw-r--r--src/gui/rhi/qrhid3d11.cpp3803
-rw-r--r--src/gui/rhi/qrhid3d11_p.h76
-rw-r--r--src/gui/rhi/qrhid3d11_p_p.h693
-rw-r--r--src/gui/rhi/qrhigles2.cpp3161
-rw-r--r--src/gui/rhi/qrhigles2_p.h84
-rw-r--r--src/gui/rhi/qrhigles2_p_p.h732
-rw-r--r--src/gui/rhi/qrhimetal.mm3566
-rw-r--r--src/gui/rhi/qrhimetal_p.h80
-rw-r--r--src/gui/rhi/qrhimetal_p_p.h450
-rw-r--r--src/gui/rhi/qrhinull.cpp764
-rw-r--r--src/gui/rhi/qrhinull_p.h69
-rw-r--r--src/gui/rhi/qrhinull_p_p.h294
-rw-r--r--src/gui/rhi/qrhiprofiler.cpp603
-rw-r--r--src/gui/rhi/qrhiprofiler_p.h120
-rw-r--r--src/gui/rhi/qrhiprofiler_p_p.h121
-rw-r--r--src/gui/rhi/qrhivulkan.cpp6071
-rw-r--r--src/gui/rhi/qrhivulkan_p.h85
-rw-r--r--src/gui/rhi/qrhivulkan_p_p.h917
-rw-r--r--src/gui/rhi/qrhivulkanext_p.h81
-rw-r--r--src/gui/rhi/qshader.cpp585
-rw-r--r--src/gui/rhi/qshader_p.h224
-rw-r--r--src/gui/rhi/qshader_p_p.h84
-rw-r--r--src/gui/rhi/qshaderdescription.cpp1116
-rw-r--r--src/gui/rhi/qshaderdescription_p.h281
-rw-r--r--src/gui/rhi/qshaderdescription_p_p.h98
-rw-r--r--src/gui/rhi/rhi.pri57
-rw-r--r--src/gui/text/qcssparser.cpp1
-rw-r--r--src/gui/text/qcssparser_p.h1
-rw-r--r--src/gui/text/qdistancefield.cpp7
-rw-r--r--src/gui/text/qdistancefield_p.h1
-rw-r--r--src/gui/text/qfont.cpp36
-rw-r--r--src/gui/text/qfontdatabase.cpp34
-rw-r--r--src/gui/text/qglyphrun.cpp2
-rw-r--r--src/gui/text/qplatformfontdatabase.cpp2
-rw-r--r--src/gui/text/qrawfont_p.h2
-rw-r--r--src/gui/text/qstatictext.cpp4
-rw-r--r--src/gui/text/qtextdocument.cpp51
-rw-r--r--src/gui/text/qtextdocument.h2
-rw-r--r--src/gui/text/qtextdocument_p.h2
-rw-r--r--src/gui/text/qtextdocumentlayout.cpp12
-rw-r--r--src/gui/text/qtexthtmlparser.cpp53
-rw-r--r--src/gui/text/qtexthtmlparser_p.h1
-rw-r--r--src/gui/text/qtextmarkdownimporter.cpp2
-rw-r--r--src/gui/text/qtextmarkdownwriter.cpp5
-rw-r--r--src/gui/text/qtextmarkdownwriter_p.h5
-rw-r--r--src/gui/util/qdesktopservices.cpp4
-rw-r--r--src/gui/util/qshaderlanguage_p.h3
-rw-r--r--src/gui/vulkan/vulkan.pri72
136 files changed, 32698 insertions, 1006 deletions
diff --git a/src/gui/.prev_CMakeLists.txt b/src/gui/.prev_CMakeLists.txt
index 815a33117a..9138f4a878 100644
--- a/src/gui/.prev_CMakeLists.txt
+++ b/src/gui/.prev_CMakeLists.txt
@@ -14,7 +14,6 @@ add_qt_module(Gui
image/qiconengineplugin.cpp image/qiconengineplugin.h
image/qiconloader.cpp image/qiconloader_p.h
image/qimage.cpp image/qimage.h image/qimage_p.h
- image/qimage_compat.cpp
image/qimage_conversions.cpp
image/qimageiohandler.cpp image/qimageiohandler.h
image/qimagepixmapcleanuphooks.cpp image/qimagepixmapcleanuphooks_p.h
@@ -151,6 +150,16 @@ add_qt_module(Gui
painting/qtriangulatingstroker.cpp painting/qtriangulatingstroker_p.h
painting/qtriangulator.cpp painting/qtriangulator_p.h
painting/qvectorpath_p.h
+ rhi/qrhi.cpp rhi/qrhi_p.h
+ rhi/qrhi_p_p.h
+ rhi/qrhinull.cpp rhi/qrhinull_p.h
+ rhi/qrhinull_p_p.h
+ rhi/qrhiprofiler.cpp rhi/qrhiprofiler_p.h
+ rhi/qrhiprofiler_p_p.h
+ rhi/qshader.cpp rhi/qshader_p.h
+ rhi/qshader_p_p.h
+ rhi/qshaderdescription.cpp rhi/qshaderdescription_p.h
+ rhi/qshaderdescription_p_p.h
text/qabstracttextdocumentlayout.cpp text/qabstracttextdocumentlayout.h text/qabstracttextdocumentlayout_p.h
text/qdistancefield.cpp text/qdistancefield_p.h
text/qfont.cpp text/qfont.h text/qfont_p.h
@@ -204,6 +213,8 @@ add_qt_module(Gui
DEFINES
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
+ INCLUDE_DIRECTORIES
+ ../3rdparty/VulkanMemoryAllocator
LIBRARIES
Qt::CorePrivate
ZLIB::ZLIB
@@ -311,6 +322,8 @@ extend_target(Gui CONDITION QT_FEATURE_opengl
opengl/qopenglversionfunctions.cpp opengl/qopenglversionfunctions.h
opengl/qopenglversionfunctionsfactory.cpp opengl/qopenglversionfunctionsfactory_p.h
opengl/qopenglvertexarrayobject.cpp opengl/qopenglvertexarrayobject.h
+ rhi/qrhigles2.cpp rhi/qrhigles2_p.h
+ rhi/qrhigles2_p_p.h
)
#### Keys ignored in scope 2:.:.:gui.pro:QT_FEATURE_opengl:
@@ -323,8 +336,14 @@ extend_target(Gui CONDITION QT_FEATURE_opengl
# _LOADED = "testcocoon"
extend_target(Gui CONDITION APPLE_OSX
+ SOURCES
+ rhi/qrhimetal.mm rhi/qrhimetal_p.h
+ rhi/qrhimetal_p_p.h
LIBRARIES
${FWAppKit}
+ PUBLIC_LIBRARIES
+ ${FWAppKit}
+ ${FWMetal}
)
extend_target(Gui CONDITION APPLE
@@ -343,6 +362,12 @@ extend_target(Gui CONDITION QT_FEATURE_animation
extend_target(Gui CONDITION WIN32
SOURCES
kernel/qwindowdefs_win.h
+ rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h
+ rhi/qrhid3d11_p_p.h
+ PUBLIC_LIBRARIES
+ d3d11
+ dxgi
+ dxguid
)
#### Keys ignored in scope 8:.:.:gui.pro:WIN32:
@@ -478,7 +503,7 @@ extend_target(Gui CONDITION GCC AND (QT_COMPILER_VERSION_MAJOR STREQUAL 5)
painting/qdrawhelper.cpp
)
-#### Keys ignored in scope 50:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND QT_ARCH___contains___arm:
+#### Keys ignored in scope 49:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND QT_ARCH___contains___arm:
# CONFIG = "no_clang_integrated_as"
extend_target(Gui CONDITION UNIX AND NOT APPLE_UIKIT AND NOT INTEGRITY AND NOT (TEST_architecture_arch STREQUAL "arm64")
@@ -486,7 +511,7 @@ extend_target(Gui CONDITION UNIX AND NOT APPLE_UIKIT AND NOT INTEGRITY AND NOT (
ENABLE_PIXMAN_DRAWHELPERS
)
-#### Keys ignored in scope 51:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND NOT INTEGRITY AND NOT QT_ARCH___contains___arm64:
+#### Keys ignored in scope 50:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND NOT INTEGRITY AND NOT QT_ARCH___contains___arm64:
# NEON_ASM = "../3rdparty/pixman/pixman-arm-neon-asm.S" "painting/qdrawhelper_neon_asm.S"
extend_target(Gui CONDITION QT_FEATURE_regularexpression
@@ -494,7 +519,7 @@ extend_target(Gui CONDITION QT_FEATURE_regularexpression
util/qshadergenerator.cpp util/qshadergenerator_p.h
)
-#### Keys ignored in scope 61:.:opengl:opengl/opengl.pri:QT_FEATURE_opengles2:
+#### Keys ignored in scope 60:.:opengl:opengl/opengl.pri:QT_FEATURE_opengles2:
# CONFIG = "opengles2"
extend_target(Gui CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_opengles2
@@ -536,6 +561,8 @@ extend_target(Gui CONDITION QT_FEATURE_opengl AND QT_FEATURE_opengles2
extend_target(Gui CONDITION QT_FEATURE_vulkan
SOURCES
+ rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h
+ rhi/qrhivulkan_p_p.h
vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h
vulkan/qvulkanfunctions.cpp
vulkan/qvulkaninstance.cpp vulkan/qvulkaninstance.h
@@ -544,9 +571,28 @@ extend_target(Gui CONDITION QT_FEATURE_vulkan
Vulkan::Vulkan_nolink
)
-#### Keys ignored in scope 68:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vulkan:
+#### Keys ignored in scope 67:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vulkan:
# CONFIG = "generated_privates"
+#### Keys ignored in scope 68:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vkgen:
+# QMAKE_EXTRA_COMPILERS = "qvkgen_h" "qvkgen_ph" "qvkgen_pimpl"
+# QMAKE_QVKGEN_INPUT = "vulkan/vk.xml"
+# QMAKE_QVKGEN_LICENSE_HEADER = "$$QT_SOURCE_TREE/header.LGPL"
+# qvkgen_h.commands = "$$QMAKE_QVKGEN" "${QMAKE_FILE_IN}" "$$shell_quote$$QMAKE_QVKGEN_LICENSE_HEADER" "${QMAKE_FILE_OUT_PATH}/${QMAKE_FILE_OUT_BASE}"
+# qvkgen_h.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_h.output = "$$OUT_PWD/vulkan/qvulkanfunctions.h"
+# qvkgen_ph.commands = "$$escape_expand\\n"
+# qvkgen_ph.depends = "$$OUT_PWD/vulkan/qvulkanfunctions.h"
+# qvkgen_ph.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_ph.output = "$$OUT_PWD/vulkan/qvulkanfunctions_p.h"
+# qvkgen_pimpl.commands = "$$escape_expand\\n"
+# qvkgen_pimpl.depends = "$$OUT_PWD/vulkan/qvulkanfunctions_p.h"
+# qvkgen_pimpl.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_pimpl.output = "$$OUT_PWD/vulkan/qvulkanfunctions_p.cpp"
+
+#### Keys ignored in scope 69:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vulkan:
+# qvkgen_h.variable_out = "HEADERS"
+
#### Keys ignored in scope 70:.:vulkan:vulkan/vulkan.pri:else:
# qvkgen_h.CONFIG = "target_predeps" "no_link"
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index a6a10faf12..7b82360015 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -57,7 +57,6 @@ add_qt_module(Gui
image/qiconengineplugin.cpp image/qiconengineplugin.h
image/qiconloader.cpp image/qiconloader_p.h
image/qimage.cpp image/qimage.h image/qimage_p.h
- image/qimage_compat.cpp
image/qimage_conversions.cpp
image/qimageiohandler.cpp image/qimageiohandler.h
image/qimagepixmapcleanuphooks.cpp image/qimagepixmapcleanuphooks_p.h
@@ -194,6 +193,16 @@ add_qt_module(Gui
painting/qtriangulatingstroker.cpp painting/qtriangulatingstroker_p.h
painting/qtriangulator.cpp painting/qtriangulator_p.h
painting/qvectorpath_p.h
+ rhi/qrhi.cpp rhi/qrhi_p.h
+ rhi/qrhi_p_p.h
+ rhi/qrhinull.cpp rhi/qrhinull_p.h
+ rhi/qrhinull_p_p.h
+ rhi/qrhiprofiler.cpp rhi/qrhiprofiler_p.h
+ rhi/qrhiprofiler_p_p.h
+ rhi/qshader.cpp rhi/qshader_p.h
+ rhi/qshader_p_p.h
+ rhi/qshaderdescription.cpp rhi/qshaderdescription_p.h
+ rhi/qshaderdescription_p_p.h
text/qabstracttextdocumentlayout.cpp text/qabstracttextdocumentlayout.h text/qabstracttextdocumentlayout_p.h
text/qdistancefield.cpp text/qdistancefield_p.h
text/qfont.cpp text/qfont.h text/qfont_p.h
@@ -247,6 +256,8 @@ add_qt_module(Gui
DEFINES
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
+ INCLUDE_DIRECTORIES
+ ../3rdparty/VulkanMemoryAllocator
QT_QPA_DEFAULT_PLATFORM_NAME="${QT_QPA_DEFAULT_PLATFORM}" # special case
LIBRARIES
Qt::CorePrivate
@@ -391,6 +402,8 @@ extend_target(Gui CONDITION QT_FEATURE_opengl
opengl/qopenglversionfunctions.cpp opengl/qopenglversionfunctions.h
opengl/qopenglversionfunctionsfactory.cpp opengl/qopenglversionfunctionsfactory_p.h
opengl/qopenglvertexarrayobject.cpp opengl/qopenglvertexarrayobject.h
+ rhi/qrhigles2.cpp rhi/qrhigles2_p.h
+ rhi/qrhigles2_p_p.h
)
#### Keys ignored in scope 2:.:.:gui.pro:QT_FEATURE_opengl:
@@ -403,8 +416,14 @@ extend_target(Gui CONDITION QT_FEATURE_opengl
# _LOADED = "testcocoon"
extend_target(Gui CONDITION APPLE_OSX
+ SOURCES
+ rhi/qrhimetal.mm rhi/qrhimetal_p.h
+ rhi/qrhimetal_p_p.h
LIBRARIES
${FWAppKit}
+ PUBLIC_LIBRARIES
+ ${FWAppKit}
+ ${FWMetal}
)
extend_target(Gui CONDITION APPLE
@@ -423,6 +442,12 @@ extend_target(Gui CONDITION QT_FEATURE_animation
extend_target(Gui CONDITION WIN32
SOURCES
kernel/qwindowdefs_win.h
+ rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h
+ rhi/qrhid3d11_p_p.h
+ PUBLIC_LIBRARIES
+ d3d11
+ dxgi
+ dxguid
)
#### Keys ignored in scope 8:.:.:gui.pro:WIN32:
@@ -563,7 +588,7 @@ extend_target(Gui CONDITION QT_FEATURE_cssparser
# extend_target(Gui CONDITION GCC AND (QT_COMPILER_VERSION_MAJOR STREQUAL 5) ... # special case: remove
-#### Keys ignored in scope 50:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND QT_ARCH___contains___arm:
+#### Keys ignored in scope 49:.:painting:painting/painting.pri:NOT APPLE_UIKIT AND NOT WIN32 AND QT_ARCH___contains___arm:
# CONFIG = "no_clang_integrated_as"
extend_target(Gui CONDITION UNIX AND NOT APPLE_UIKIT AND NOT INTEGRITY AND NOT (TEST_architecture_arch STREQUAL "arm64")
@@ -587,7 +612,7 @@ extend_target(Gui CONDITION QT_FEATURE_regularexpression
util/qshadergenerator.cpp util/qshadergenerator_p.h
)
-#### Keys ignored in scope 61:.:opengl:opengl/opengl.pri:QT_FEATURE_opengles2:
+#### Keys ignored in scope 60:.:opengl:opengl/opengl.pri:QT_FEATURE_opengles2:
# CONFIG = "opengles2"
extend_target(Gui CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_opengles2
@@ -637,6 +662,8 @@ endif()
extend_target(Gui CONDITION QT_FEATURE_vulkan
SOURCES
+ rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h
+ rhi/qrhivulkan_p_p.h
vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h
vulkan/qvulkanfunctions.cpp
vulkan/qvulkaninstance.cpp vulkan/qvulkaninstance.h
@@ -680,6 +707,25 @@ endif()
#### Keys ignored in scope 68:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vulkan:
# CONFIG = "generated_privates"
+#### Keys ignored in scope 68:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vkgen:
+# QMAKE_EXTRA_COMPILERS = "qvkgen_h" "qvkgen_ph" "qvkgen_pimpl"
+# QMAKE_QVKGEN_INPUT = "vulkan/vk.xml"
+# QMAKE_QVKGEN_LICENSE_HEADER = "$$QT_SOURCE_TREE/header.LGPL"
+# qvkgen_h.commands = "$$QMAKE_QVKGEN" "${QMAKE_FILE_IN}" "$$shell_quote$$QMAKE_QVKGEN_LICENSE_HEADER" "${QMAKE_FILE_OUT_PATH}/${QMAKE_FILE_OUT_BASE}"
+# qvkgen_h.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_h.output = "$$OUT_PWD/vulkan/qvulkanfunctions.h"
+# qvkgen_ph.commands = "$$escape_expand\\n"
+# qvkgen_ph.depends = "$$OUT_PWD/vulkan/qvulkanfunctions.h"
+# qvkgen_ph.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_ph.output = "$$OUT_PWD/vulkan/qvulkanfunctions_p.h"
+# qvkgen_pimpl.commands = "$$escape_expand\\n"
+# qvkgen_pimpl.depends = "$$OUT_PWD/vulkan/qvulkanfunctions_p.h"
+# qvkgen_pimpl.input = "QMAKE_QVKGEN_INPUT"
+# qvkgen_pimpl.output = "$$OUT_PWD/vulkan/qvulkanfunctions_p.cpp"
+
+#### Keys ignored in scope 69:.:vulkan:vulkan/vulkan.pri:QT_FEATURE_vulkan:
+# qvkgen_h.variable_out = "HEADERS"
+
#### Keys ignored in scope 70:.:vulkan:vulkan/vulkan.pri:else:
# qvkgen_h.CONFIG = "target_predeps" "no_link"
diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp
index 68e36de24b..db47a3abc1 100644
--- a/src/gui/accessible/qaccessible.cpp
+++ b/src/gui/accessible/qaccessible.cpp
@@ -350,6 +350,7 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value MenuItem An item in a menu or menu bar.
\value NoRole The object has no role. This usually indicates an invalid object.
\value Note A section whose content is parenthetic or ancillary to the main content of the resource.
+ \value Notification An object that represents a notification (e.g. in the system tray). This role only has an effect on Linux.
\value PageTab A page tab that the user can select to switch to a different page in a dialog.
\value PageTabList A list of page tabs.
\value Paragraph A paragraph of text (usually found in documents).
diff --git a/src/gui/accessible/qaccessible.h b/src/gui/accessible/qaccessible.h
index e2163b06d0..2220efd5cb 100644
--- a/src/gui/accessible/qaccessible.h
+++ b/src/gui/accessible/qaccessible.h
@@ -299,6 +299,7 @@ public:
Paragraph = 0x00000083,
WebDocument = 0x00000084,
Section = 0x00000085,
+ Notification = 0x00000086,
// IAccessible2 roles
// IA2_ROLE_CANVAS = 0x401, ### Qt 6 use this one instead of Canvas above
diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake
index d8cd47db49..f674311ed2 100644
--- a/src/gui/configure.cmake
+++ b/src/gui/configure.cmake
@@ -665,9 +665,13 @@ qt_feature("opengl" PUBLIC
CONDITION QT_FEATURE_opengl_desktop OR QT_FEATURE_opengl_dynamic OR QT_FEATURE_opengles2
)
qt_feature_definition("opengl" "QT_NO_OPENGL" NEGATE VALUE "1")
+qt_feature("vkgen" PRIVATE
+ LABEL "vkgen"
+ CONDITION QT_FEATURE_xmlstreamreader
+)
qt_feature("vulkan" PUBLIC
LABEL "Vulkan"
- CONDITION Vulkan_FOUND
+ CONDITION QT_FEATURE_vkgen AND Vulkan_FOUND
)
qt_feature("openvg" PUBLIC
LABEL "OpenVG"
diff --git a/src/gui/configure.json b/src/gui/configure.json
index ff783935dc..7a55efaf85 100644
--- a/src/gui/configure.json
+++ b/src/gui/configure.json
@@ -1356,9 +1356,14 @@
"condition": "features.opengl-desktop || features.opengl-dynamic || features.opengles2",
"output": [ "publicFeature", "feature" ]
},
+ "vkgen": {
+ "label": "vkgen",
+ "condition": "features.xmlstreamreader",
+ "output": [ "privateFeature" ]
+ },
"vulkan": {
"label": "Vulkan",
- "condition": "libs.vulkan",
+ "condition": "features.vkgen && libs.vulkan",
"output": [ "publicFeature" ]
},
"openvg": {
diff --git a/src/gui/doc/qtgui.qdocconf b/src/gui/doc/qtgui.qdocconf
index b8b8a00cd6..049b9ef179 100644
--- a/src/gui/doc/qtgui.qdocconf
+++ b/src/gui/doc/qtgui.qdocconf
@@ -1,4 +1,5 @@
include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+include($QT_INSTALL_DOCS/config/exampleurl-qtbase.qdocconf)
project = QtGui
description = Qt GUI Reference Documentation
diff --git a/src/gui/doc/snippets/code/doc_src_coordsys.cpp b/src/gui/doc/snippets/code/doc_src_coordsys.cpp
index 7c53d9e731..147c146b44 100644
--- a/src/gui/doc/snippets/code/doc_src_coordsys.cpp
+++ b/src/gui/doc/snippets/code/doc_src_coordsys.cpp
@@ -52,6 +52,7 @@
QPainter painter(this);
painter.setPen(Qt::darkGreen);
+// Using the (x y w h) overload
painter.drawRect(1, 2, 6, 4);
//! [0]
@@ -69,6 +70,7 @@ QPainter painter(this);
painter.setRenderHint(
QPainter::Antialiasing);
painter.setPen(Qt::darkGreen);
+// Using the (x y w h) overload
painter.drawRect(1, 2, 6, 4);
//! [2]
diff --git a/src/gui/doc/snippets/textblock-fragments/mainwindow.cpp b/src/gui/doc/snippets/textblock-fragments/mainwindow.cpp
index bf864ce48d..236d6952e6 100644
--- a/src/gui/doc/snippets/textblock-fragments/mainwindow.cpp
+++ b/src/gui/doc/snippets/textblock-fragments/mainwindow.cpp
@@ -114,7 +114,7 @@ void MainWindow::insertCalendar()
int year = date.year(), month = date.month();
for (int weekDay = 1; weekDay <= 7; ++weekDay) {
- cursor.insertText(QString("%1 ").arg(QDate::shortDayName(weekDay), 3),
+ cursor.insertText(QString("%1 ").arg(QLocale::system().dayName(weekDay), 3),
boldFormat);
}
diff --git a/src/gui/doc/snippets/textdocument-blocks/mainwindow.cpp b/src/gui/doc/snippets/textdocument-blocks/mainwindow.cpp
index a5801da67e..849f0e957f 100644
--- a/src/gui/doc/snippets/textdocument-blocks/mainwindow.cpp
+++ b/src/gui/doc/snippets/textdocument-blocks/mainwindow.cpp
@@ -117,7 +117,7 @@ void MainWindow::insertCalendar()
int year = date.year(), month = date.month();
for (int weekDay = 1; weekDay <= 7; ++weekDay) {
- cursor.insertText(QString("%1 ").arg(QDate::shortDayName(weekDay), 3),
+ cursor.insertText(QString("%1 ").arg(QLocale::system().dayName(weekDay), 3),
boldFormat);
}
diff --git a/src/gui/doc/src/coordsys.qdoc b/src/gui/doc/src/coordsys.qdoc
index 1856428361..1018b2122b 100644
--- a/src/gui/doc/src/coordsys.qdoc
+++ b/src/gui/doc/src/coordsys.qdoc
@@ -70,8 +70,15 @@
\li \inlineimage coordinatesystem-rect.png
\li \inlineimage coordinatesystem-line.png
\row
- \li QRect(1, 2, 6, 4)
+ \li QRect(QPoint(1, 2), QPoint(7, 6))
+ \li QLine(QPoint(2, 7), QPoint(6, 1))
+ \row
+ \li
\li QLine(2, 7, 6, 1)
+ \row
+ \li QRect(QPoint(1, 2), QSize(6, 4))
+ \row
+ \li QRect(1, 2, 6, 4)
\endtable
\section2 Aliased Painting
diff --git a/src/gui/doc/src/dontdocument.qdoc b/src/gui/doc/src/dontdocument.qdoc
new file mode 100644
index 0000000000..b360acefc1
--- /dev/null
+++ b/src/gui/doc/src/dontdocument.qdoc
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \dontdocument (QTypeInfo QScreenOrientationChangeEvent QApplicationStateChangeEvent
+ QImageTextKeyLang QMetaTypeId QAbstractUndoItem
+ QOpenGLVersionStatus
+ QOpenGLVersionFunctionsBackend
+ QOpenGLVersionFunctionsStorage
+ QOpenGLFunctions_1_0_CoreBackend
+ QOpenGLFunctions_1_1_CoreBackend
+ QOpenGLFunctions_1_2_CoreBackend
+ QOpenGLFunctions_1_3_CoreBackend
+ QOpenGLFunctions_1_4_CoreBackend
+ QOpenGLFunctions_1_5_CoreBackend
+ QOpenGLFunctions_2_0_CoreBackend
+ QOpenGLFunctions_2_1_CoreBackend
+ QOpenGLFunctions_3_0_CoreBackend
+ QOpenGLFunctions_3_1_CoreBackend
+ QOpenGLFunctions_3_2_CoreBackend
+ QOpenGLFunctions_3_3_CoreBackend
+ QOpenGLFunctions_4_0_CoreBackend
+ QOpenGLFunctions_4_1_CoreBackend
+ QOpenGLFunctions_4_2_CoreBackend
+ QOpenGLFunctions_4_3_CoreBackend
+ QOpenGLFunctions_4_4_CoreBackend
+ QOpenGLFunctions_4_5_CoreBackend
+ QOpenGLFunctions_1_0_DeprecatedBackend
+ QOpenGLFunctions_1_1_DeprecatedBackend
+ QOpenGLFunctions_1_2_DeprecatedBackend
+ QOpenGLFunctions_1_3_DeprecatedBackend
+ QOpenGLFunctions_1_4_DeprecatedBackend
+ QOpenGLFunctions_2_0_DeprecatedBackend
+ QOpenGLFunctions_3_0_DeprecatedBackend
+ QOpenGLFunctions_3_3_DeprecatedBackend
+ QOpenGLFunctions_4_5_DeprecatedBackend
+ QTextFrameLayoutData QPlatformDropQtResponse QPlatformDragQtResponse
+ QPlatformOffscreenSurface QColorDialogOptions QFontDialogOptions
+ QFileDialogOptions QMessageDialogOptions QMessageDialogOptions::CustomButton
+ QPlatformSessionManager QPlatformIntegrationPlugin QPlatformMenuItem
+ QPlatformMenu QPlatformMenuBar QPlatformTextureList)
+*/
diff --git a/src/gui/gui.pro b/src/gui/gui.pro
index edf8124081..45c8c05162 100644
--- a/src/gui/gui.pro
+++ b/src/gui/gui.pro
@@ -49,6 +49,7 @@ qtConfig(animation): include(animation/animation.pri)
include(itemmodels/itemmodels.pri)
include(vulkan/vulkan.pri)
include(platform/platform.pri)
+include(rhi/rhi.pri)
QMAKE_LIBS += $$QMAKE_LIBS_GUI
diff --git a/src/gui/image/image.pri b/src/gui/image/image.pri
index 70fccbc378..b482a6e142 100644
--- a/src/gui/image/image.pri
+++ b/src/gui/image/image.pri
@@ -59,9 +59,6 @@ win32:!winrt: SOURCES += image/qpixmap_win.cpp
darwin: OBJECTIVE_SOURCES += image/qimage_darwin.mm
-NO_PCH_SOURCES += image/qimage_compat.cpp
-false: SOURCES += $$NO_PCH_SOURCES # Hack for QtCreator
-
# Built-in image format support
HEADERS += \
image/qbmphandler_p.h \
diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp
index 1d1b6d3c49..df8220a0c6 100644
--- a/src/gui/image/qicon.cpp
+++ b/src/gui/image/qicon.cpp
@@ -987,7 +987,7 @@ bool QIcon::isNull() const
*/
bool QIcon::isDetached() const
{
- return !d || d->ref.load() == 1;
+ return !d || d->ref.loadRelaxed() == 1;
}
/*! \internal
@@ -1000,7 +1000,7 @@ void QIcon::detach()
delete d;
d = 0;
return;
- } else if (d->ref.load() != 1) {
+ } else if (d->ref.loadRelaxed() != 1) {
QIconPrivate *x = new QIconPrivate(d->engine->clone());
if (!d->ref.deref())
delete d;
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp
index 917dde3b0f..61d32b0dec 100644
--- a/src/gui/image/qimage.cpp
+++ b/src/gui/image/qimage.cpp
@@ -1082,10 +1082,10 @@ QImage::operator QVariant() const
void QImage::detach()
{
if (d) {
- if (d->is_cached && d->ref.load() == 1)
+ if (d->is_cached && d->ref.loadRelaxed() == 1)
QImagePixmapCleanupHooks::executeImageHooks(cacheKey());
- if (d->ref.load() != 1 || d->ro_data)
+ if (d->ref.loadRelaxed() != 1 || d->ro_data)
*this = copy();
if (d)
@@ -4422,7 +4422,7 @@ qint64 QImage::cacheKey() const
bool QImage::isDetached() const
{
- return d && d->ref.load() == 1;
+ return d && d->ref.loadRelaxed() == 1;
}
@@ -5087,7 +5087,7 @@ bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFla
return true;
// No in-place conversion if we have to detach
- if (ref.load() > 1 || !own_data)
+ if (ref.loadRelaxed() > 1 || !own_data)
return false;
InPlace_Image_Converter converter = qimage_inplace_converter_map[format][newFormat];
diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h
index 7c68168be8..3f0d900a70 100644
--- a/src/gui/image/qimage.h
+++ b/src/gui/image/qimage.h
@@ -178,7 +178,6 @@ public:
Format format() const;
-#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(QT_COMPILING_QIMAGE_COMPAT_CPP)
Q_REQUIRED_RESULT Q_ALWAYS_INLINE QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) const &
{ return convertToFormat_helper(f, flags); }
Q_REQUIRED_RESULT Q_ALWAYS_INLINE QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) &&
@@ -188,9 +187,6 @@ public:
else
return convertToFormat_helper(f, flags);
}
-#else
- Q_REQUIRED_RESULT QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) const;
-#endif
Q_REQUIRED_RESULT QImage convertToFormat(Format f, const QVector<QRgb> &colorTable, Qt::ImageConversionFlags flags = Qt::AutoColor) const;
bool reinterpretAsFormat(Format f);
@@ -283,7 +279,6 @@ public:
static QMatrix trueMatrix(const QMatrix &, int w, int h);
QImage transformed(const QTransform &matrix, Qt::TransformationMode mode = Qt::FastTransformation) const;
static QTransform trueMatrix(const QTransform &, int w, int h);
-#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(QT_COMPILING_QIMAGE_COMPAT_CPP)
QImage mirrored(bool horizontally = false, bool vertically = true) const &
{ return mirrored_helper(horizontally, vertically); }
QImage &&mirrored(bool horizontally = false, bool vertically = true) &&
@@ -292,10 +287,6 @@ public:
{ return rgbSwapped_helper(); }
QImage &&rgbSwapped() &&
{ rgbSwapped_inplace(); return std::move(*this); }
-#else
- QImage mirrored(bool horizontally = false, bool vertically = true) const;
- QImage rgbSwapped() const;
-#endif
void invertPixels(InvertMode = InvertRgb);
QColorSpace colorSpace() const;
diff --git a/src/gui/image/qimage_compat.cpp b/src/gui/image/qimage_compat.cpp
deleted file mode 100644
index ba31a9ac9b..0000000000
--- a/src/gui/image/qimage_compat.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifdef QIMAGE_H
-# error "This file cannot be used with precompiled headers"
-#endif
-#define QT_COMPILING_QIMAGE_COMPAT_CPP
-
-#include "qimage.h"
-
-QT_BEGIN_NAMESPACE
-
-// These implementations must be the same as the inline versions in qimage.h
-
-QImage QImage::convertToFormat(Format f, Qt::ImageConversionFlags flags) const
-{
- return convertToFormat_helper(f, flags);
-}
-
-QImage QImage::mirrored(bool horizontally, bool vertically) const
-{
- return mirrored_helper(horizontally, vertically);
-}
-
-QImage QImage::rgbSwapped() const
-{
- return rgbSwapped_helper();
-}
-
-QT_END_NAMESPACE
diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp
index ae433ff651..2762b702b2 100644
--- a/src/gui/image/qimagereader.cpp
+++ b/src/gui/image/qimagereader.cpp
@@ -1321,10 +1321,12 @@ bool QImageReader::read(QImage *image)
}
}
- // successful read; check for "@2x" file name suffix and set device pixel ratio.
- static bool disable2xImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
- if (!disable2xImageLoading && QFileInfo(fileName()).baseName().endsWith(QLatin1String("@2x"))) {
- image->setDevicePixelRatio(2.0);
+ // successful read; check for "@Nx" file name suffix and set device pixel ratio.
+ static bool disableNxImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
+ if (!disableNxImageLoading) {
+ const QByteArray suffix = QFileInfo(fileName()).baseName().right(3).toLatin1();
+ if (suffix.length() == 3 && suffix[0] == '@' && suffix[1] >= '2' && suffix[1] <= '9' && suffix[2] == 'x')
+ image->setDevicePixelRatio(suffix[1] - '0');
}
if (autoTransform())
qt_imageTransform(*image, transformation());
diff --git a/src/gui/image/qmovie.cpp b/src/gui/image/qmovie.cpp
index 79203c7b98..4b588527ae 100644
--- a/src/gui/image/qmovie.cpp
+++ b/src/gui/image/qmovie.cpp
@@ -175,11 +175,12 @@
#include "qmovie.h"
#include "qglobal.h"
+#include "qelapsedtimer.h"
#include "qimage.h"
#include "qimagereader.h"
#include "qpixmap.h"
#include "qrect.h"
-#include "qdatetime.h"
+#include "qelapsedtimer.h"
#include "qtimer.h"
#include "qpair.h"
#include "qmap.h"
@@ -437,7 +438,7 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
*/
bool QMoviePrivate::next()
{
- QTime time;
+ QElapsedTimer time;
time.start();
QFrameInfo info = infoForFrame(nextFrameNumber);
if (!info.isValid())
diff --git a/src/gui/image/qpicture.cpp b/src/gui/image/qpicture.cpp
index fe1e4d2c9b..8548f1857e 100644
--- a/src/gui/image/qpicture.cpp
+++ b/src/gui/image/qpicture.cpp
@@ -235,7 +235,7 @@ void QPicture::detach()
bool QPicture::isDetached() const
{
- return d_func()->ref.load() == 1;
+ return d_func()->ref.loadRelaxed() == 1;
}
/*!
diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp
index 399ad7453d..b6e41f16a5 100644
--- a/src/gui/image/qpixmap.cpp
+++ b/src/gui/image/qpixmap.cpp
@@ -262,7 +262,7 @@ QPixmap::QPixmap(const char * const xpm[])
QPixmap::~QPixmap()
{
- Q_ASSERT(!data || data->ref.load() >= 1); // Catch if ref-counting changes again
+ Q_ASSERT(!data || data->ref.loadRelaxed() >= 1); // Catch if ref-counting changes again
}
/*!
@@ -910,7 +910,7 @@ void QPixmap::fill(const QColor &color)
return;
}
- if (data->ref.load() == 1) {
+ if (data->ref.loadRelaxed() == 1) {
// detach() will also remove this pixmap from caches, so
// it has to be called even when ref == 1.
detach();
@@ -1053,7 +1053,7 @@ QDataStream &operator>>(QDataStream &stream, QPixmap &pixmap)
bool QPixmap::isDetached() const
{
- return data && data->ref.load() == 1;
+ return data && data->ref.loadRelaxed() == 1;
}
/*!
@@ -1523,10 +1523,10 @@ void QPixmap::detach()
rasterData->image.detach();
}
- if (data->is_cached && data->ref.load() == 1)
+ if (data->is_cached && data->ref.loadRelaxed() == 1)
QImagePixmapCleanupHooks::executePlatformPixmapModificationHooks(data.data());
- if (data->ref.load() != 1) {
+ if (data->ref.loadRelaxed() != 1) {
*this = copy();
}
++data->detach_no;
diff --git a/src/gui/image/qpnghandler.cpp b/src/gui/image/qpnghandler.cpp
index 39d0807606..864a944fcb 100644
--- a/src/gui/image/qpnghandler.cpp
+++ b/src/gui/image/qpnghandler.cpp
@@ -417,7 +417,7 @@ void setup_qt(QImage& image, png_structp png_ptr, png_infop info_ptr, QSize scal
}
QSize outSize(width,height);
if (!scaledSize.isEmpty() && quint32(scaledSize.width()) <= width &&
- quint32(scaledSize.height()) <= height && interlace_method == PNG_INTERLACE_NONE) {
+ quint32(scaledSize.height()) <= height && scaledSize != outSize && interlace_method == PNG_INTERLACE_NONE) {
// Do inline downscaling
outSize = scaledSize;
if (doScaledRead)
diff --git a/src/gui/image/qxpmhandler.cpp b/src/gui/image/qxpmhandler.cpp
index deff56aa58..cf105b250a 100644
--- a/src/gui/image/qxpmhandler.cpp
+++ b/src/gui/image/qxpmhandler.cpp
@@ -1125,8 +1125,6 @@ static bool write_xpm_image(const QImage &sourceImage, QIODevice *device, const
break;
}
- QString line;
-
// write header
QTextStream s(device);
s << "/* XPM */" << Qt::endl
@@ -1137,35 +1135,29 @@ static bool write_xpm_image(const QImage &sourceImage, QIODevice *device, const
QMap<QRgb, int>::Iterator c = colorMap.begin();
while (c != colorMap.end()) {
QRgb color = c.key();
- if (image.format() != QImage::Format_RGB32 && !qAlpha(color))
- line = QString::asprintf("\"%s c None\"",
- xpm_color_name(cpp, *c));
- else
- line = QString::asprintf("\"%s c #%02x%02x%02x\"",
- xpm_color_name(cpp, *c),
- qRed(color),
- qGreen(color),
- qBlue(color));
+ const QString line = image.format() != QImage::Format_RGB32 && !qAlpha(color)
+ ? QString::asprintf("\"%s c None\"", xpm_color_name(cpp, *c))
+ : QString::asprintf("\"%s c #%02x%02x%02x\"", xpm_color_name(cpp, *c),
+ qRed(color), qGreen(color), qBlue(color));
++c;
s << ',' << Qt::endl << line;
}
// write pixels, limit to 4 characters per pixel
- line.truncate(cpp*w);
+ QByteArray line;
for(y=0; y<h; y++) {
+ line.clear();
const QRgb *yp = reinterpret_cast<const QRgb *>(image.constScanLine(y));
- int cc = 0;
for(x=0; x<w; x++) {
int color = (int)(*(yp + x));
const QByteArray chars(xpm_color_name(cpp, colorMap[color]));
- line[cc++] = QLatin1Char(chars[0]);
+ line.append(chars[0]);
if (cpp > 1) {
- line[cc++] = QLatin1Char(chars[1]);
+ line.append(chars[1]);
if (cpp > 2) {
- line[cc++] = QLatin1Char(chars[2]);
- if (cpp > 3) {
- line[cc++] = QLatin1Char(chars[3]);
- }
+ line.append(chars[2]);
+ if (cpp > 3)
+ line.append(chars[3]);
}
}
}
diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp
index 2f3a02f4d6..e1d685b1b0 100644
--- a/src/gui/kernel/qevent.cpp
+++ b/src/gui/kernel/qevent.cpp
@@ -97,35 +97,35 @@ QEnterEvent::~QEnterEvent()
/*!
\fn QPoint QEnterEvent::globalPos() const
- Returns the global position of the widget \e{at the time of the event}.
+ Returns the global position of the mouse cursor \e{at the time of the event}.
*/
/*!
\fn int QEnterEvent::globalX() const
- Returns the global position on the X-axis of the mouse cursor relative to the the widget.
+ Returns the global position on the X-axis of the mouse cursor \e{at the time of the event}.
*/
/*!
\fn int QEnterEvent::globalY() const
- Returns the global position on the Y-axis of the mouse cursor relative to the the widget.
+ Returns the global position on the Y-axis of the mouse cursor \e{at the time of the event}.
*/
/*!
- \fn QPoint QEnterEvent::localPos() const
+ \fn QPointF QEnterEvent::localPos() const
Returns the mouse cursor's position relative to the receiving widget.
*/
/*!
\fn QPoint QEnterEvent::pos() const
- Returns the position of the mouse cursor in global screen coordinates.
+ Returns the position of the mouse cursor relative to the receiving widget.
*/
/*!
- \fn QPoint QEnterEvent::screenPos() const
+ \fn QPointF QEnterEvent::screenPos() const
Returns the position of the mouse cursor relative to the receiving screen.
*/
/*!
- \fn QPoint QEnterEvent::windowPos() const
+ \fn QPointF QEnterEvent::windowPos() const
Returns the position of the mouse cursor relative to the receiving window.
*/
@@ -4921,7 +4921,7 @@ QVector<QPointF> QTouchEvent::TouchPoint::rawScreenPositions() const
/*! \internal */
void QTouchEvent::TouchPoint::setId(int id)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->id = id;
}
@@ -4929,7 +4929,7 @@ void QTouchEvent::TouchPoint::setId(int id)
/*! \internal */
void QTouchEvent::TouchPoint::setUniqueId(qint64 uid)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->uniqueId = QPointingDeviceUniqueId::fromNumericId(uid);
}
@@ -4937,7 +4937,7 @@ void QTouchEvent::TouchPoint::setUniqueId(qint64 uid)
/*! \internal */
void QTouchEvent::TouchPoint::setState(Qt::TouchPointStates state)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->state = state;
}
@@ -4945,7 +4945,7 @@ void QTouchEvent::TouchPoint::setState(Qt::TouchPointStates state)
/*! \internal */
void QTouchEvent::TouchPoint::setPos(const QPointF &pos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->pos = pos;
}
@@ -4953,7 +4953,7 @@ void QTouchEvent::TouchPoint::setPos(const QPointF &pos)
/*! \internal */
void QTouchEvent::TouchPoint::setScenePos(const QPointF &scenePos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->scenePos = scenePos;
}
@@ -4961,7 +4961,7 @@ void QTouchEvent::TouchPoint::setScenePos(const QPointF &scenePos)
/*! \internal */
void QTouchEvent::TouchPoint::setScreenPos(const QPointF &screenPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->screenPos = screenPos;
}
@@ -4969,7 +4969,7 @@ void QTouchEvent::TouchPoint::setScreenPos(const QPointF &screenPos)
/*! \internal */
void QTouchEvent::TouchPoint::setNormalizedPos(const QPointF &normalizedPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->normalizedPos = normalizedPos;
}
@@ -4977,7 +4977,7 @@ void QTouchEvent::TouchPoint::setNormalizedPos(const QPointF &normalizedPos)
/*! \internal */
void QTouchEvent::TouchPoint::setStartPos(const QPointF &startPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->startPos = startPos;
}
@@ -4985,7 +4985,7 @@ void QTouchEvent::TouchPoint::setStartPos(const QPointF &startPos)
/*! \internal */
void QTouchEvent::TouchPoint::setStartScenePos(const QPointF &startScenePos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->startScenePos = startScenePos;
}
@@ -4993,7 +4993,7 @@ void QTouchEvent::TouchPoint::setStartScenePos(const QPointF &startScenePos)
/*! \internal */
void QTouchEvent::TouchPoint::setStartScreenPos(const QPointF &startScreenPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->startScreenPos = startScreenPos;
}
@@ -5001,7 +5001,7 @@ void QTouchEvent::TouchPoint::setStartScreenPos(const QPointF &startScreenPos)
/*! \internal */
void QTouchEvent::TouchPoint::setStartNormalizedPos(const QPointF &startNormalizedPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->startNormalizedPos = startNormalizedPos;
}
@@ -5009,7 +5009,7 @@ void QTouchEvent::TouchPoint::setStartNormalizedPos(const QPointF &startNormaliz
/*! \internal */
void QTouchEvent::TouchPoint::setLastPos(const QPointF &lastPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->lastPos = lastPos;
}
@@ -5017,7 +5017,7 @@ void QTouchEvent::TouchPoint::setLastPos(const QPointF &lastPos)
/*! \internal */
void QTouchEvent::TouchPoint::setLastScenePos(const QPointF &lastScenePos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->lastScenePos = lastScenePos;
}
@@ -5025,7 +5025,7 @@ void QTouchEvent::TouchPoint::setLastScenePos(const QPointF &lastScenePos)
/*! \internal */
void QTouchEvent::TouchPoint::setLastScreenPos(const QPointF &lastScreenPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->lastScreenPos = lastScreenPos;
}
@@ -5033,7 +5033,7 @@ void QTouchEvent::TouchPoint::setLastScreenPos(const QPointF &lastScreenPos)
/*! \internal */
void QTouchEvent::TouchPoint::setLastNormalizedPos(const QPointF &lastNormalizedPos)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->lastNormalizedPos = lastNormalizedPos;
}
@@ -5044,7 +5044,7 @@ void QTouchEvent::TouchPoint::setLastNormalizedPos(const QPointF &lastNormalized
*/
void QTouchEvent::TouchPoint::setRect(const QRectF &rect)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->pos = rect.center();
d->ellipseDiameters = rect.size();
@@ -5055,7 +5055,7 @@ void QTouchEvent::TouchPoint::setRect(const QRectF &rect)
*/
void QTouchEvent::TouchPoint::setSceneRect(const QRectF &sceneRect)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->scenePos = sceneRect.center();
d->ellipseDiameters = sceneRect.size();
@@ -5066,7 +5066,7 @@ void QTouchEvent::TouchPoint::setSceneRect(const QRectF &sceneRect)
*/
void QTouchEvent::TouchPoint::setScreenRect(const QRectF &screenRect)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->screenPos = screenRect.center();
d->ellipseDiameters = screenRect.size();
@@ -5075,7 +5075,7 @@ void QTouchEvent::TouchPoint::setScreenRect(const QRectF &screenRect)
/*! \internal */
void QTouchEvent::TouchPoint::setPressure(qreal pressure)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->pressure = pressure;
}
@@ -5083,7 +5083,7 @@ void QTouchEvent::TouchPoint::setPressure(qreal pressure)
/*! \internal */
void QTouchEvent::TouchPoint::setRotation(qreal angle)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->rotation = angle;
}
@@ -5091,7 +5091,7 @@ void QTouchEvent::TouchPoint::setRotation(qreal angle)
/*! \internal */
void QTouchEvent::TouchPoint::setEllipseDiameters(const QSizeF &dia)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->ellipseDiameters = dia;
}
@@ -5099,7 +5099,7 @@ void QTouchEvent::TouchPoint::setEllipseDiameters(const QSizeF &dia)
/*! \internal */
void QTouchEvent::TouchPoint::setVelocity(const QVector2D &v)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->velocity = v;
}
@@ -5107,7 +5107,7 @@ void QTouchEvent::TouchPoint::setVelocity(const QVector2D &v)
/*! \internal */
void QTouchEvent::TouchPoint::setRawScreenPositions(const QVector<QPointF> &positions)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->rawScreenPositions = positions;
}
@@ -5117,7 +5117,7 @@ void QTouchEvent::TouchPoint::setRawScreenPositions(const QVector<QPointF> &posi
*/
void QTouchEvent::TouchPoint::setFlags(InfoFlags flags)
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d = d->detach();
d->flags = flags;
}
diff --git a/src/gui/kernel/qevent_p.h b/src/gui/kernel/qevent_p.h
index 7df4a1e25b..c2d8bd72b9 100644
--- a/src/gui/kernel/qevent_p.h
+++ b/src/gui/kernel/qevent_p.h
@@ -73,7 +73,7 @@ public:
inline QTouchEventTouchPointPrivate *detach()
{
QTouchEventTouchPointPrivate *d = new QTouchEventTouchPointPrivate(*this);
- d->ref.store(1);
+ d->ref.storeRelaxed(1);
if (!this->ref.deref())
delete this;
return d;
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
index 7799be77d2..ceb5055a9d 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
@@ -1306,6 +1306,7 @@ static void init_plugins(const QList<QByteArray> &pluginList)
}
}
+#if QT_CONFIG(commandlineparser)
void QGuiApplicationPrivate::addQtOptions(QList<QCommandLineOption> *options)
{
QCoreApplicationPrivate::addQtOptions(options);
@@ -1357,6 +1358,7 @@ void QGuiApplicationPrivate::addQtOptions(QList<QCommandLineOption> *options)
QGuiApplication::tr("Alias for --windowtitle."), QStringLiteral("title")));
}
}
+#endif // QT_CONFIG(commandlineparser)
void QGuiApplicationPrivate::createPlatformIntegration()
{
@@ -3260,9 +3262,12 @@ void QGuiApplication::setPalette(const QPalette &pal)
QGuiApplicationPrivate::app_pal = new QPalette(pal);
else
*QGuiApplicationPrivate::app_pal = pal;
+
applicationResourceFlags |= ApplicationPaletteExplicitlySet;
QCoreApplication::setAttribute(Qt::AA_SetPalette);
- emit qGuiApp->paletteChanged(*QGuiApplicationPrivate::app_pal);
+
+ if (qGuiApp)
+ emit qGuiApp->paletteChanged(*QGuiApplicationPrivate::app_pal);
}
void QGuiApplicationPrivate::applyWindowGeometrySpecificationTo(QWindow *window)
diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h
index 6902a99eca..afca7579ea 100644
--- a/src/gui/kernel/qguiapplication_p.h
+++ b/src/gui/kernel/qguiapplication_p.h
@@ -92,7 +92,9 @@ public:
virtual void notifyLayoutDirectionChange();
virtual void notifyActiveWindowChange(QWindow *previous);
+#if QT_CONFIG(commandlineparser)
void addQtOptions(QList<QCommandLineOption> *options) override;
+#endif
virtual bool shouldQuit() override;
bool shouldQuitInternal(const QWindowList &processedWindows);
@@ -114,7 +116,7 @@ public:
static QAbstractEventDispatcher *qt_qpa_core_dispatcher()
{
if (QCoreApplication::instance())
- return QCoreApplication::instance()->d_func()->threadData->eventDispatcher.load();
+ return QCoreApplication::instance()->d_func()->threadData->eventDispatcher.loadRelaxed();
else
return nullptr;
}
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
index 4f8e9a3817..0fea416404 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -400,7 +400,7 @@ QPoint QHighDpiScaling::mapPositionToGlobal(const QPoint &pos, const QPoint &win
QPoint QHighDpiScaling::mapPositionFromGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window)
{
QPoint windowPosCandidate = pos - windowGlobalPosition;
- if (QGuiApplicationPrivate::screen_list.size() <= 1)
+ if (QGuiApplicationPrivate::screen_list.size() <= 1 || window->handle() == nullptr)
return windowPosCandidate;
// Device independent global (screen) space may discontiguous when high-dpi scaling
@@ -452,52 +452,30 @@ QDpi QHighDpiScaling::logicalDpi()
return m_logicalDpi;
}
-qreal QHighDpiScaling::factor(const QScreen *screen)
+QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)
{
- // Fast path for when scaling in Qt is not used at all.
if (!m_active)
- return qreal(1.0);
-
- // The effective factor for a given screen is the product of the
- // screen and global sub-factors
- qreal factor = m_factor;
- if (screen)
- factor *= screenSubfactor(screen->handle());
- return factor;
+ return { qreal(1), QPoint() };
+ const QPlatformScreen *actualScreen = nativePosition ?
+ platformScreen->screenForPosition(*nativePosition) : platformScreen;
+ return { m_factor * screenSubfactor(actualScreen), actualScreen->geometry().topLeft() };
}
-qreal QHighDpiScaling::factor(const QPlatformScreen *platformScreen)
+QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QPoint *nativePosition)
{
if (!m_active)
- return qreal(1.0);
-
- return m_factor * screenSubfactor(platformScreen);
+ return { qreal(1), QPoint() };
+ if (!screen)
+ return { m_factor, QPoint() }; // the global factor
+ return scaleAndOrigin(screen->handle(), nativePosition);
}
-qreal QHighDpiScaling::factor(const QWindow *window)
+QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
{
if (!m_active)
- return qreal(1.0);
-
- return factor(window ? window->screen() : QGuiApplication::primaryScreen());
-}
-
-QPoint QHighDpiScaling::origin(const QScreen *screen)
-{
- return screen->geometry().topLeft();
-}
-
-QPoint QHighDpiScaling::origin(const QPlatformScreen *platformScreen)
-{
- return platformScreen->geometry().topLeft();
-}
-
-QPoint QHighDpiScaling::origin(const QWindow *window)
-{
- if (window && window->isTopLevel() && window->screen())
- return window->screen()->geometry().topLeft();
-
- return QPoint(0, 0);
+ return { qreal(1), QPoint() };
+ QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
+ return scaleAndOrigin(screen, nativePosition);
}
#endif //QT_NO_HIGHDPISCALING
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
index 91cfbcbfc7..50221926c6 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -78,14 +78,23 @@ public:
static void setScreenFactor(QScreen *window, qreal factor);
static bool isActive() { return m_active; }
- static qreal factor(const QWindow *window);
- static qreal factor(const QScreen *screen);
- static qreal factor(const QPlatformScreen *platformScreen);
- static QPoint origin(const QScreen *screen);
- static QPoint origin(const QPlatformScreen *platformScreen);
- static QPoint origin(const QWindow *window);
- static QPoint mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen);
+
+ struct ScaleAndOrigin
+ {
+ qreal factor;
+ QPoint origin;
+ };
+ static ScaleAndOrigin scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition = nullptr);
+ static ScaleAndOrigin scaleAndOrigin(const QScreen *screen, QPoint *nativePosition = nullptr);
+ static ScaleAndOrigin scaleAndOrigin(const QWindow *platformScreen, QPoint *nativePosition = nullptr);
+
+ template<typename C>
+ static qreal factor(C *context, QPoint *nativePosition = nullptr) {
+ return scaleAndOrigin(context, nativePosition).factor;
+ }
+
static QPoint mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen);
+ static QPoint mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen);
static QPoint mapPositionToGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window);
static QPoint mapPositionFromGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window);
static QDpi logicalDpi();
@@ -108,208 +117,124 @@ private:
namespace QHighDpi {
-inline QPointF fromNative(const QPointF &pos, qreal scaleFactor, const QPointF &origin)
+template <typename T>
+inline T scale(const T &value, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- return (pos - origin) / scaleFactor + origin;
+ Q_UNUSED(origin)
+ return value * scaleFactor;
}
-inline QPointF toNative(const QPointF &pos, qreal scaleFactor, const QPointF &origin)
+inline QPointF scale(const QPointF &pos, qreal scaleFactor, QPointF origin = QPointF(0, 0))
{
return (pos - origin) * scaleFactor + origin;
}
-inline QPoint fromNative(const QPoint &pos, qreal scaleFactor, const QPoint &origin)
-{
- return (pos - origin) / scaleFactor + origin;
-}
-
-inline QPoint toNative(const QPoint &pos, qreal scaleFactor, const QPoint &origin)
+inline QPoint scale(const QPoint &pos, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
return (pos - origin) * scaleFactor + origin;
}
-inline QPoint fromNative(const QPoint &pos, qreal scaleFactor)
-{
- return pos / scaleFactor;
-}
-
-inline QPoint toNative(const QPoint &pos, qreal scaleFactor)
-{
- return pos * scaleFactor;
-}
-
-inline QSize fromNative(const QSize &size, qreal scaleFactor)
-{
- return size / scaleFactor; // TODO: should we round up?
-}
-
-inline QSize toNative(const QSize &size, qreal scaleFactor)
-{
- return size * scaleFactor;
-}
-
-inline QSizeF fromNative(const QSizeF &size, qreal scaleFactor)
-{
- return size / scaleFactor;
-}
-
-inline QSizeF toNative(const QSizeF &size, qreal scaleFactor)
-{
- return size * scaleFactor;
-}
-
-inline QRect fromNative(const QRect &rect, qreal scaleFactor, const QPoint &origin)
+inline QRect scale(const QRect &rect, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- return QRect(fromNative(rect.topLeft(), scaleFactor, origin), fromNative(rect.size(), scaleFactor));
+ return QRect(scale(rect.topLeft(), scaleFactor, origin), scale(rect.size(), scaleFactor));
}
-inline QRect toNative(const QRect &rect, qreal scaleFactor, const QPoint &origin)
+inline QRectF scale(const QRectF &rect, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- return QRect(toNative(rect.topLeft(), scaleFactor, origin), toNative(rect.size(), scaleFactor));
-
-}
-
-inline QRect fromNative(const QRect &rect, const QScreen *screen, const QPoint &screenOrigin)
-{
- return fromNative(rect, QHighDpiScaling::factor(screen), screenOrigin);
-}
-
-inline QRect fromNativeScreenGeometry(const QRect &nativeScreenGeometry, const QScreen *screen)
-{
- return QRect(nativeScreenGeometry.topLeft(),
- fromNative(nativeScreenGeometry.size(), QHighDpiScaling::factor(screen)));
-}
-
-inline QPoint fromNativeLocalPosition(const QPoint &pos, const QWindow *window)
-{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return pos / scaleFactor;
-}
-
-inline QPoint toNativeLocalPosition(const QPoint &pos, const QWindow *window)
-{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return pos * scaleFactor;
-}
-
-inline QPointF fromNativeLocalPosition(const QPointF &pos, const QWindow *window)
-{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return pos / scaleFactor;
-}
-
-inline QPointF toNativeLocalPosition(const QPointF &pos, const QWindow *window)
-{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return pos * scaleFactor;
+ return QRectF(scale(rect.topLeft(), scaleFactor, origin), scale(rect.size(), scaleFactor));
}
-template <typename C>
-inline QRect fromNativePixels(const QRect &pixelRect, const C *context)
+inline QMargins scale(const QMargins &margins, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- const qreal scaleFactor = QHighDpiScaling::factor(context);
- const QPoint origin = QHighDpiScaling::origin(context);
- return QRect(fromNative(pixelRect.topLeft(), scaleFactor, origin),
- fromNative(pixelRect.size(), scaleFactor));
+ Q_UNUSED(origin)
+ return QMargins(qRound(qreal(margins.left()) * scaleFactor), qRound(qreal(margins.top()) * scaleFactor),
+ qRound(qreal(margins.right()) * scaleFactor), qRound(qreal(margins.bottom()) * scaleFactor));
}
-template <typename C>
-inline QRect toNativePixels(const QRect &pointRect, const C *context)
+template <typename T>
+QVector<T> scale(const QVector<T> &vector, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- const qreal scaleFactor = QHighDpiScaling::factor(context);
- const QPoint origin = QHighDpiScaling::origin(context);
- return QRect(toNative(pointRect.topLeft(), scaleFactor, origin),
- toNative(pointRect.size(), scaleFactor));
-}
+ if (!QHighDpiScaling::isActive())
+ return vector;
-template <typename C>
-inline QRectF toNativePixels(const QRectF &pointRect, const C *context)
-{
- const qreal scaleFactor = QHighDpiScaling::factor(context);
- const QPoint origin = QHighDpiScaling::origin(context);
- return QRectF(toNative(pointRect.topLeft(), scaleFactor, origin),
- toNative(pointRect.size(), scaleFactor));
+ QVector<T> scaled;
+ scaled.reserve(vector.size());
+ for (const T &item : vector)
+ scaled.append(scale(item, scaleFactor, origin));
+ return scaled;
}
-template <typename C>
-inline QRectF fromNativePixels(const QRectF &pixelRect, const C *context)
+inline QRegion scale(const QRegion &region, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- const qreal scaleFactor = QHighDpiScaling::factor(context);
- const QPoint origin = QHighDpiScaling::origin(context);
- return QRectF(fromNative(pixelRect.topLeft(), scaleFactor, origin),
- fromNative(pixelRect.size(), scaleFactor));
-}
+ if (!QHighDpiScaling::isActive())
+ return region;
-inline QSize fromNativePixels(const QSize &pixelSize, const QWindow *window)
-{
- return pixelSize / QHighDpiScaling::factor(window);
+ QRegion scaled;
+ for (const QRect &rect : region)
+ scaled += scale(rect, scaleFactor, origin);
+ return scaled;
}
-inline QSize toNativePixels(const QSize &pointSize, const QWindow *window)
-{
- return pointSize * QHighDpiScaling::factor(window);
-}
+template <typename T>
+inline QPoint position(T) { return QPoint(); }
+inline QPoint position(QPoint point) { return point; }
+inline QPoint position(QPointF point) { return point.toPoint(); }
+inline QPoint position(QRect rect) { return rect.center(); }
+inline QPoint position(QRectF rect) { return rect.center().toPoint(); }
-inline QSizeF fromNativePixels(const QSizeF &pixelSize, const QWindow *window)
+template <typename T, typename C>
+T fromNativePixels(const T &value, const C *context)
{
- return pixelSize / QHighDpiScaling::factor(window);
+ QPoint nativePosition = position(value);
+ QHighDpiScaling::ScaleAndOrigin so = QHighDpiScaling::scaleAndOrigin(context, &nativePosition);
+ return scale(value, qreal(1) / so.factor, so.origin);
}
-inline QSizeF toNativePixels(const QSizeF &pointSize, const QWindow *window)
+template <typename T, typename C>
+T toNativePixels(const T &value, const C *context)
{
- return pointSize * QHighDpiScaling::factor(window);
+ QHighDpiScaling::ScaleAndOrigin so = QHighDpiScaling::scaleAndOrigin(context);
+ return scale(value, so.factor, so.origin);
}
-template <typename C>
-inline QPoint fromNativePixels(const QPoint &pixelPoint, const C *context)
+template <typename T, typename C>
+T fromNativeLocalPosition(const T &value, const C *context)
{
- return fromNative(pixelPoint, QHighDpiScaling::factor(context), QHighDpiScaling::origin(context));
+ return scale(value, qreal(1) / QHighDpiScaling::factor(context));
}
-template <typename C>
-inline QPoint toNativePixels(const QPoint &pointPoint, const C *context)
+template <typename T, typename C>
+T toNativeLocalPosition(const T &value, const C *context)
{
- return toNative(pointPoint, QHighDpiScaling::factor(context), QHighDpiScaling::origin(context));
+ return scale(value, QHighDpiScaling::factor(context));
}
-template <typename C>
-inline QPointF fromNativePixels(const QPointF &pixelPoint, const C *context)
+template <typename T>
+inline T fromNative(const T &value, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- return fromNative(pixelPoint, QHighDpiScaling::factor(context), QHighDpiScaling::origin(context));
+ return scale(value, qreal(1) / scaleFactor, origin);
}
-template <typename C>
-inline QPointF toNativePixels(const QPointF &pointPoint, const C *context)
+template <typename T>
+inline T toNative(const T &value, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- return toNative(pointPoint, QHighDpiScaling::factor(context), QHighDpiScaling::origin(context));
+ return scale(value, scaleFactor, origin);
}
-inline QMargins fromNativePixels(const QMargins &pixelMargins, const QWindow *window)
+inline QRect fromNative(const QRect &rect, const QScreen *screen, const QPoint &screenOrigin)
{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return QMargins(pixelMargins.left() / scaleFactor, pixelMargins.top() / scaleFactor,
- pixelMargins.right() / scaleFactor, pixelMargins.bottom() / scaleFactor);
+ return scale(rect, qreal(1) / QHighDpiScaling::factor(screen), screenOrigin);
}
-inline QMargins toNativePixels(const QMargins &pointMargins, const QWindow *window)
+inline QRect fromNativeScreenGeometry(const QRect &nativeScreenGeometry, const QScreen *screen)
{
- const qreal scaleFactor = QHighDpiScaling::factor(window);
- return QMargins(pointMargins.left() * scaleFactor, pointMargins.top() * scaleFactor,
- pointMargins.right() * scaleFactor, pointMargins.bottom() * scaleFactor);
+ return QRect(nativeScreenGeometry.topLeft(),
+ scale(nativeScreenGeometry.size(), qreal(1) / QHighDpiScaling::factor(screen)));
}
inline QRegion fromNativeLocalRegion(const QRegion &pixelRegion, const QWindow *window)
{
- if (!QHighDpiScaling::isActive())
- return pixelRegion;
-
- qreal scaleFactor = QHighDpiScaling::factor(window);
- QRegion pointRegion;
- for (const QRect &rect : pixelRegion) {
- pointRegion += QRect(fromNative(rect.topLeft(), scaleFactor),
- fromNative(rect.size(), scaleFactor));
- }
- return pointRegion;
+ return scale(pixelRegion, qreal(1) / QHighDpiScaling::factor(window));
}
// When mapping expose events to Qt rects: round top/left towards the origin and
@@ -333,67 +258,7 @@ inline QRegion fromNativeLocalExposedRegion(const QRegion &pixelRegion, const QW
inline QRegion toNativeLocalRegion(const QRegion &pointRegion, const QWindow *window)
{
- if (!QHighDpiScaling::isActive())
- return pointRegion;
-
- qreal scaleFactor = QHighDpiScaling::factor(window);
- QRegion pixelRegon;
- for (const QRect &rect : pointRegion) {
- pixelRegon += QRect(toNative(rect.topLeft(), scaleFactor),
- toNative(rect.size(), scaleFactor));
- }
- return pixelRegon;
-}
-
-// Any T that has operator/()
-template <typename T, typename C>
-T fromNativePixels(const T &pixelValue, const C *context)
-{
- if (!QHighDpiScaling::isActive())
- return pixelValue;
-
- return pixelValue / QHighDpiScaling::factor(context);
-
-}
-
-// Any T that has operator*()
-template <typename T, typename C>
-T toNativePixels(const T &pointValue, const C *context)
-{
- if (!QHighDpiScaling::isActive())
- return pointValue;
-
- return pointValue * QHighDpiScaling::factor(context);
-}
-
-// Any QVector<T> where T has operator/()
-template <typename T>
-QVector<T> fromNativePixels(const QVector<T> &pixelValues, const QWindow *window)
-{
- if (!QHighDpiScaling::isActive())
- return pixelValues;
-
- QVector<T> pointValues;
- pointValues.reserve(pixelValues.size());
- const auto factor = QHighDpiScaling::factor(window);
- for (const T &pixelValue : pixelValues)
- pointValues.append(pixelValue / factor);
- return pointValues;
-}
-
-// Any QVector<T> where T has operator*()
-template <typename T>
-QVector<T> toNativePixels(const QVector<T> &pointValues, const QWindow *window)
-{
- if (!QHighDpiScaling::isActive())
- return pointValues;
-
- QVector<T> pixelValues;
- pixelValues.reserve(pointValues.size());
- const auto factor = QHighDpiScaling::factor(window);
- for (const T &pointValue : pointValues)
- pixelValues.append(pointValue * factor);
- return pixelValues;
+ return scale(pointRegion, QHighDpiScaling::factor(window));
}
} // namespace QHighDpi
diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp
index 8688bb8403..2a86b340af 100644
--- a/src/gui/kernel/qkeysequence.cpp
+++ b/src/gui/kernel/qkeysequence.cpp
@@ -1518,7 +1518,7 @@ bool QKeySequence::operator< (const QKeySequence &other) const
*/
bool QKeySequence::isDetached() const
{
- return d->ref.load() == 1;
+ return d->ref.loadRelaxed() == 1;
}
/*!
diff --git a/src/gui/kernel/qopenglcontext.cpp b/src/gui/kernel/qopenglcontext.cpp
index 5bc6e27eb2..326d2823eb 100644
--- a/src/gui/kernel/qopenglcontext.cpp
+++ b/src/gui/kernel/qopenglcontext.cpp
@@ -1610,8 +1610,7 @@ void QOpenGLSharedResourceGuard::freeResource(QOpenGLContext *context)
QOpenGLMultiGroupSharedResource instance.
*/
QOpenGLMultiGroupSharedResource::QOpenGLMultiGroupSharedResource()
- : active(0),
- m_mutex(QMutex::Recursive)
+ : active(0)
{
#ifdef QT_GL_CONTEXT_RESOURCE_DEBUG
qDebug("Creating context group resource object %p.", this);
@@ -1634,7 +1633,7 @@ QOpenGLMultiGroupSharedResource::~QOpenGLMultiGroupSharedResource()
active.deref();
}
#ifndef QT_NO_DEBUG
- if (active.load() != 0) {
+ if (active.loadRelaxed() != 0) {
qWarning("QtGui: Resources are still available at program shutdown.\n"
" This is possibly caused by a leaked QOpenGLWidget, \n"
" QOpenGLFramebufferObject or QOpenGLPixelBuffer.");
diff --git a/src/gui/kernel/qopenglcontext_p.h b/src/gui/kernel/qopenglcontext_p.h
index c6ad893ee6..833cfb20c3 100644
--- a/src/gui/kernel/qopenglcontext_p.h
+++ b/src/gui/kernel/qopenglcontext_p.h
@@ -132,7 +132,6 @@ class Q_GUI_EXPORT QOpenGLContextGroupPrivate : public QObjectPrivate
public:
QOpenGLContextGroupPrivate()
: m_context(nullptr)
- , m_mutex(QMutex::Recursive)
, m_refs(0)
{
}
@@ -147,7 +146,7 @@ public:
QOpenGLContext *m_context;
QList<QOpenGLContext *> m_shares;
- QMutex m_mutex;
+ QRecursiveMutex m_mutex;
QHash<QOpenGLMultiGroupSharedResource *, QOpenGLSharedResource *> m_resources;
QAtomicInt m_refs;
@@ -186,7 +185,7 @@ public:
private:
QAtomicInt active;
QList<QOpenGLContextGroup *> m_groups;
- QMutex m_mutex;
+ QRecursiveMutex m_mutex;
};
class QPaintEngineEx;
diff --git a/src/gui/kernel/qopenglwindow.cpp b/src/gui/kernel/qopenglwindow.cpp
index 8b052d92ae..022a47c919 100644
--- a/src/gui/kernel/qopenglwindow.cpp
+++ b/src/gui/kernel/qopenglwindow.cpp
@@ -440,7 +440,7 @@ void QOpenGLWindow::makeCurrent()
d->context->makeCurrent(this);
} else {
if (!d->offscreenSurface) {
- d->offscreenSurface.reset(new QOffscreenSurface);
+ d->offscreenSurface.reset(new QOffscreenSurface(screen()));
d->offscreenSurface->setFormat(d->context->format());
d->offscreenSurface->create();
}
diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp
index f6e5fa0a52..61dccd77ac 100644
--- a/src/gui/kernel/qpalette.cpp
+++ b/src/gui/kernel/qpalette.cpp
@@ -830,7 +830,7 @@ bool QPalette::isBrushSet(ColorGroup cg, ColorRole cr) const
*/
void QPalette::detach()
{
- if (d->ref.load() != 1) {
+ if (d->ref.loadRelaxed() != 1) {
QPalettePrivate *x = new QPalettePrivate;
for(int grp = 0; grp < (int)NColorGroups; grp++) {
for(int role = 0; role < (int)NColorRoles; role++)
@@ -1008,6 +1008,8 @@ QDataStream &operator<<(QDataStream &s, const QPalette &p)
max = QPalette::HighlightedText + 1;
else if (s.version() <= QDataStream::Qt_4_3)
max = QPalette::AlternateBase + 1;
+ else if (s.version() <= QDataStream::Qt_5_11)
+ max = QPalette::ToolTipText + 1;
for (int r = 0; r < max; r++)
s << p.d->br[grp][r];
}
@@ -1048,6 +1050,9 @@ QDataStream &operator>>(QDataStream &s, QPalette &p)
} else if (s.version() <= QDataStream::Qt_4_3) {
p = QPalette();
max = QPalette::AlternateBase + 1;
+ } else if (s.version() <= QDataStream::Qt_5_11) {
+ p = QPalette();
+ max = QPalette::ToolTipText + 1;
}
QBrush tmp;
diff --git a/src/gui/kernel/qsimpledrag.cpp b/src/gui/kernel/qsimpledrag.cpp
index bba2a863a9..d3070a3d1a 100644
--- a/src/gui/kernel/qsimpledrag.cpp
+++ b/src/gui/kernel/qsimpledrag.cpp
@@ -309,11 +309,15 @@ void QBasicDrag::updateCursor(Qt::DropAction action)
m_dndHasSetOverrideCursor = true;
} else {
QCursor *cursor = QGuiApplication::overrideCursor();
- if (!pixmap.isNull()) {
- if (cursor->pixmap().cacheKey() != pixmap.cacheKey())
- QGuiApplication::changeOverrideCursor(QCursor(pixmap));
- } else if (cursorShape != cursor->shape()) {
- QGuiApplication::changeOverrideCursor(QCursor(cursorShape));
+ if (!cursor) {
+ QGuiApplication::changeOverrideCursor(pixmap.isNull() ? QCursor(cursorShape) : QCursor(pixmap));
+ } else {
+ if (!pixmap.isNull()) {
+ if (cursor->pixmap().cacheKey() != pixmap.cacheKey())
+ QGuiApplication::changeOverrideCursor(QCursor(pixmap));
+ } else if (cursorShape != cursor->shape()) {
+ QGuiApplication::changeOverrideCursor(QCursor(cursorShape));
+ }
}
}
#endif
diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp
index 1a814ec21f..2e2738ec81 100644
--- a/src/gui/kernel/qsurfaceformat.cpp
+++ b/src/gui/kernel/qsurfaceformat.cpp
@@ -246,7 +246,7 @@ QSurfaceFormat::QSurfaceFormat(QSurfaceFormat::FormatOptions options) :
*/
void QSurfaceFormat::detach()
{
- if (d->ref.load() != 1) {
+ if (d->ref.loadRelaxed() != 1) {
QSurfaceFormatPrivate *newd = new QSurfaceFormatPrivate(d);
if (!d->ref.deref())
delete d;
diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp
index aad5f856be..b71a0c54aa 100644
--- a/src/gui/kernel/qwindow.cpp
+++ b/src/gui/kernel/qwindow.cpp
@@ -218,6 +218,12 @@ QWindow::~QWindow()
QGuiApplicationPrivate::window_list.removeAll(this);
if (!QGuiApplicationPrivate::is_app_closing)
QGuiApplicationPrivate::instance()->modalWindowList.removeOne(this);
+
+ // focus_window is normally cleared in destroy(), but the window may in
+ // some cases end up becoming the focus window again. Clear it again
+ // here as a workaround. See QTBUG-75326.
+ if (QGuiApplicationPrivate::focus_window == this)
+ QGuiApplicationPrivate::focus_window = 0;
}
void QWindowPrivate::init(QScreen *targetScreen)
diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp
index 6f3edb10b4..4b8cb3646a 100644
--- a/src/gui/kernel/qwindowsysteminterface.cpp
+++ b/src/gui/kernel/qwindowsysteminterface.cpp
@@ -1115,7 +1115,7 @@ bool QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ProcessEventsFl
} else {
sendWindowSystemEvents(flags);
}
- return QWindowSystemInterfacePrivate::eventAccepted.load() > 0;
+ return QWindowSystemInterfacePrivate::eventAccepted.loadRelaxed() > 0;
}
void QWindowSystemInterface::deferredFlushWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
@@ -1156,7 +1156,7 @@ bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFla
// (excluding flush events). This state can then be
// returned by flushWindowSystemEvents().
if (event->type != QWindowSystemInterfacePrivate::FlushEvents)
- QWindowSystemInterfacePrivate::eventAccepted.store(event->eventAccepted);
+ QWindowSystemInterfacePrivate::eventAccepted.storeRelaxed(event->eventAccepted);
delete event;
}
diff --git a/src/gui/math3d/qvector3d.cpp b/src/gui/math3d/qvector3d.cpp
index 12a7902272..4f72c1da66 100644
--- a/src/gui/math3d/qvector3d.cpp
+++ b/src/gui/math3d/qvector3d.cpp
@@ -514,7 +514,7 @@ float QVector3D::distanceToPlane
/*!
\overload
- Returns the distance from this vertex a plane defined by
+ Returns the distance from this vertex to a plane defined by
the vertices \a plane1, \a plane2 and \a plane3.
The return value will be negative if the vertex is below the plane,
diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp
index e7631b09ce..5d30891565 100644
--- a/src/gui/opengl/qopenglframebufferobject.cpp
+++ b/src/gui/opengl/qopenglframebufferobject.cpp
@@ -183,7 +183,7 @@ QT_BEGIN_NAMESPACE
*/
void QOpenGLFramebufferObjectFormat::detach()
{
- if (d->ref.load() != 1) {
+ if (d->ref.loadRelaxed() != 1) {
QOpenGLFramebufferObjectFormatPrivate *newd
= new QOpenGLFramebufferObjectFormatPrivate(d);
if (!d->ref.deref())
diff --git a/src/gui/opengl/qopenglfunctions_1_0.cpp b/src/gui/opengl/qopenglfunctions_1_0.cpp
index 4235c9a339..f017c68fd9 100644
--- a/src/gui/opengl/qopenglfunctions_1_0.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_0.cpp
@@ -76,11 +76,11 @@ QOpenGLFunctions_1_0::~QOpenGLFunctions_1_0()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_1_1.cpp b/src/gui/opengl/qopenglfunctions_1_1.cpp
index 7d09bb40c1..a819d499f8 100644
--- a/src/gui/opengl/qopenglfunctions_1_1.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_1.cpp
@@ -78,19 +78,19 @@ QOpenGLFunctions_1_1::~QOpenGLFunctions_1_1()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_1_2.cpp b/src/gui/opengl/qopenglfunctions_1_2.cpp
index 94a9d64660..61db2b4e0f 100644
--- a/src/gui/opengl/qopenglfunctions_1_2.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_2.cpp
@@ -80,27 +80,27 @@ QOpenGLFunctions_1_2::~QOpenGLFunctions_1_2()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_1_3.cpp b/src/gui/opengl/qopenglfunctions_1_3.cpp
index 972ef9ff70..acc223ea74 100644
--- a/src/gui/opengl/qopenglfunctions_1_3.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_3.cpp
@@ -82,35 +82,35 @@ QOpenGLFunctions_1_3::~QOpenGLFunctions_1_3()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_1_4.cpp b/src/gui/opengl/qopenglfunctions_1_4.cpp
index 4b78253301..8e2349dc08 100644
--- a/src/gui/opengl/qopenglfunctions_1_4.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_4.cpp
@@ -84,43 +84,43 @@ QOpenGLFunctions_1_4::~QOpenGLFunctions_1_4()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_1_5.cpp b/src/gui/opengl/qopenglfunctions_1_5.cpp
index 2a0820d0cb..cd81cf8b35 100644
--- a/src/gui/opengl/qopenglfunctions_1_5.cpp
+++ b/src/gui/opengl/qopenglfunctions_1_5.cpp
@@ -85,47 +85,47 @@ QOpenGLFunctions_1_5::~QOpenGLFunctions_1_5()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_2_0.cpp b/src/gui/opengl/qopenglfunctions_2_0.cpp
index 212723aa00..97a8c72fa6 100644
--- a/src/gui/opengl/qopenglfunctions_2_0.cpp
+++ b/src/gui/opengl/qopenglfunctions_2_0.cpp
@@ -87,51 +87,51 @@ QOpenGLFunctions_2_0::~QOpenGLFunctions_2_0()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_2_1.cpp b/src/gui/opengl/qopenglfunctions_2_1.cpp
index b8b255014c..00bdc1bbba 100644
--- a/src/gui/opengl/qopenglfunctions_2_1.cpp
+++ b/src/gui/opengl/qopenglfunctions_2_1.cpp
@@ -88,55 +88,55 @@ QOpenGLFunctions_2_1::~QOpenGLFunctions_2_1()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_0.cpp b/src/gui/opengl/qopenglfunctions_3_0.cpp
index 4972c03b1e..2c239dba1f 100644
--- a/src/gui/opengl/qopenglfunctions_3_0.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_0.cpp
@@ -90,59 +90,59 @@ QOpenGLFunctions_3_0::~QOpenGLFunctions_3_0()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_1.cpp b/src/gui/opengl/qopenglfunctions_3_1.cpp
index 9328f5ca89..f62f555c8e 100644
--- a/src/gui/opengl/qopenglfunctions_3_1.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_1.cpp
@@ -84,43 +84,43 @@ QOpenGLFunctions_3_1::~QOpenGLFunctions_3_1()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_2_compatibility.cpp b/src/gui/opengl/qopenglfunctions_3_2_compatibility.cpp
index 709f65edf8..ba7be2d893 100644
--- a/src/gui/opengl/qopenglfunctions_3_2_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_2_compatibility.cpp
@@ -92,67 +92,67 @@ QOpenGLFunctions_3_2_Compatibility::~QOpenGLFunctions_3_2_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_2_core.cpp b/src/gui/opengl/qopenglfunctions_3_2_core.cpp
index 02c0c78b01..4c1e3eb3da 100644
--- a/src/gui/opengl/qopenglfunctions_3_2_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_2_core.cpp
@@ -85,47 +85,47 @@ QOpenGLFunctions_3_2_Core::~QOpenGLFunctions_3_2_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_3_compatibility.cpp b/src/gui/opengl/qopenglfunctions_3_3_compatibility.cpp
index b034391c86..c750c6e0cc 100644
--- a/src/gui/opengl/qopenglfunctions_3_3_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_3_compatibility.cpp
@@ -93,75 +93,75 @@ QOpenGLFunctions_3_3_Compatibility::~QOpenGLFunctions_3_3_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_3_3_core.cpp b/src/gui/opengl/qopenglfunctions_3_3_core.cpp
index 7779d92b6a..5723509e32 100644
--- a/src/gui/opengl/qopenglfunctions_3_3_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_3_3_core.cpp
@@ -86,51 +86,51 @@ QOpenGLFunctions_3_3_Core::~QOpenGLFunctions_3_3_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_0_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_0_compatibility.cpp
index 4fe4526efc..6ae7643eb5 100644
--- a/src/gui/opengl/qopenglfunctions_4_0_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_0_compatibility.cpp
@@ -94,79 +94,79 @@ QOpenGLFunctions_4_0_Compatibility::~QOpenGLFunctions_4_0_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_0_core.cpp b/src/gui/opengl/qopenglfunctions_4_0_core.cpp
index 4e4e8cc547..cd4fdb8b2b 100644
--- a/src/gui/opengl/qopenglfunctions_4_0_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_0_core.cpp
@@ -87,55 +87,55 @@ QOpenGLFunctions_4_0_Core::~QOpenGLFunctions_4_0_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_1_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_1_compatibility.cpp
index 41ecb4672a..d104c74bc2 100644
--- a/src/gui/opengl/qopenglfunctions_4_1_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_1_compatibility.cpp
@@ -95,83 +95,83 @@ QOpenGLFunctions_4_1_Compatibility::~QOpenGLFunctions_4_1_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_1_core.cpp b/src/gui/opengl/qopenglfunctions_4_1_core.cpp
index 5a1e1eb42f..7527aba620 100644
--- a/src/gui/opengl/qopenglfunctions_4_1_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_1_core.cpp
@@ -88,59 +88,59 @@ QOpenGLFunctions_4_1_Core::~QOpenGLFunctions_4_1_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_2_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_2_compatibility.cpp
index fcc049c67b..a5b1b37495 100644
--- a/src/gui/opengl/qopenglfunctions_4_2_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_2_compatibility.cpp
@@ -96,87 +96,87 @@ QOpenGLFunctions_4_2_Compatibility::~QOpenGLFunctions_4_2_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_2_core.cpp b/src/gui/opengl/qopenglfunctions_4_2_core.cpp
index fdfb4db455..1381236926 100644
--- a/src/gui/opengl/qopenglfunctions_4_2_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_2_core.cpp
@@ -89,63 +89,63 @@ QOpenGLFunctions_4_2_Core::~QOpenGLFunctions_4_2_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_3_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_3_compatibility.cpp
index 131ebc810f..5c0c711d1c 100644
--- a/src/gui/opengl/qopenglfunctions_4_3_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_3_compatibility.cpp
@@ -97,91 +97,91 @@ QOpenGLFunctions_4_3_Compatibility::~QOpenGLFunctions_4_3_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_3_core.cpp b/src/gui/opengl/qopenglfunctions_4_3_core.cpp
index 95e2d7bc43..34460b841e 100644
--- a/src/gui/opengl/qopenglfunctions_4_3_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_3_core.cpp
@@ -90,67 +90,67 @@ QOpenGLFunctions_4_3_Core::~QOpenGLFunctions_4_3_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_4_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_4_compatibility.cpp
index 5c7170b8fa..907994a3c4 100644
--- a/src/gui/opengl/qopenglfunctions_4_4_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_4_compatibility.cpp
@@ -97,95 +97,95 @@ QOpenGLFunctions_4_4_Compatibility::~QOpenGLFunctions_4_4_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
if (d_4_4_Core) {
d_4_4_Core->refs.deref();
- Q_ASSERT(d_4_4_Core->refs.load());
+ Q_ASSERT(d_4_4_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_4_core.cpp b/src/gui/opengl/qopenglfunctions_4_4_core.cpp
index 54833f9058..76c0323f6d 100644
--- a/src/gui/opengl/qopenglfunctions_4_4_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_4_core.cpp
@@ -91,71 +91,71 @@ QOpenGLFunctions_4_4_Core::~QOpenGLFunctions_4_4_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
if (d_4_4_Core) {
d_4_4_Core->refs.deref();
- Q_ASSERT(d_4_4_Core->refs.load());
+ Q_ASSERT(d_4_4_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_5_compatibility.cpp b/src/gui/opengl/qopenglfunctions_4_5_compatibility.cpp
index f9d2bb9ceb..c415bb06ff 100644
--- a/src/gui/opengl/qopenglfunctions_4_5_compatibility.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_5_compatibility.cpp
@@ -99,103 +99,103 @@ QOpenGLFunctions_4_5_Compatibility::~QOpenGLFunctions_4_5_Compatibility()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
if (d_4_4_Core) {
d_4_4_Core->refs.deref();
- Q_ASSERT(d_4_4_Core->refs.load());
+ Q_ASSERT(d_4_4_Core->refs.loadRelaxed());
}
if (d_4_5_Core) {
d_4_5_Core->refs.deref();
- Q_ASSERT(d_4_5_Core->refs.load());
+ Q_ASSERT(d_4_5_Core->refs.loadRelaxed());
}
if (d_1_0_Deprecated) {
d_1_0_Deprecated->refs.deref();
- Q_ASSERT(d_1_0_Deprecated->refs.load());
+ Q_ASSERT(d_1_0_Deprecated->refs.loadRelaxed());
}
if (d_1_1_Deprecated) {
d_1_1_Deprecated->refs.deref();
- Q_ASSERT(d_1_1_Deprecated->refs.load());
+ Q_ASSERT(d_1_1_Deprecated->refs.loadRelaxed());
}
if (d_1_2_Deprecated) {
d_1_2_Deprecated->refs.deref();
- Q_ASSERT(d_1_2_Deprecated->refs.load());
+ Q_ASSERT(d_1_2_Deprecated->refs.loadRelaxed());
}
if (d_1_3_Deprecated) {
d_1_3_Deprecated->refs.deref();
- Q_ASSERT(d_1_3_Deprecated->refs.load());
+ Q_ASSERT(d_1_3_Deprecated->refs.loadRelaxed());
}
if (d_1_4_Deprecated) {
d_1_4_Deprecated->refs.deref();
- Q_ASSERT(d_1_4_Deprecated->refs.load());
+ Q_ASSERT(d_1_4_Deprecated->refs.loadRelaxed());
}
if (d_3_3_Deprecated) {
d_3_3_Deprecated->refs.deref();
- Q_ASSERT(d_3_3_Deprecated->refs.load());
+ Q_ASSERT(d_3_3_Deprecated->refs.loadRelaxed());
}
if (d_4_5_Deprecated) {
d_4_5_Deprecated->refs.deref();
- Q_ASSERT(d_4_5_Deprecated->refs.load());
+ Q_ASSERT(d_4_5_Deprecated->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglfunctions_4_5_core.cpp b/src/gui/opengl/qopenglfunctions_4_5_core.cpp
index a47ebb9ee9..4dfac3579c 100644
--- a/src/gui/opengl/qopenglfunctions_4_5_core.cpp
+++ b/src/gui/opengl/qopenglfunctions_4_5_core.cpp
@@ -92,75 +92,75 @@ QOpenGLFunctions_4_5_Core::~QOpenGLFunctions_4_5_Core()
{
if (d_1_0_Core) {
d_1_0_Core->refs.deref();
- Q_ASSERT(d_1_0_Core->refs.load());
+ Q_ASSERT(d_1_0_Core->refs.loadRelaxed());
}
if (d_1_1_Core) {
d_1_1_Core->refs.deref();
- Q_ASSERT(d_1_1_Core->refs.load());
+ Q_ASSERT(d_1_1_Core->refs.loadRelaxed());
}
if (d_1_2_Core) {
d_1_2_Core->refs.deref();
- Q_ASSERT(d_1_2_Core->refs.load());
+ Q_ASSERT(d_1_2_Core->refs.loadRelaxed());
}
if (d_1_3_Core) {
d_1_3_Core->refs.deref();
- Q_ASSERT(d_1_3_Core->refs.load());
+ Q_ASSERT(d_1_3_Core->refs.loadRelaxed());
}
if (d_1_4_Core) {
d_1_4_Core->refs.deref();
- Q_ASSERT(d_1_4_Core->refs.load());
+ Q_ASSERT(d_1_4_Core->refs.loadRelaxed());
}
if (d_1_5_Core) {
d_1_5_Core->refs.deref();
- Q_ASSERT(d_1_5_Core->refs.load());
+ Q_ASSERT(d_1_5_Core->refs.loadRelaxed());
}
if (d_2_0_Core) {
d_2_0_Core->refs.deref();
- Q_ASSERT(d_2_0_Core->refs.load());
+ Q_ASSERT(d_2_0_Core->refs.loadRelaxed());
}
if (d_2_1_Core) {
d_2_1_Core->refs.deref();
- Q_ASSERT(d_2_1_Core->refs.load());
+ Q_ASSERT(d_2_1_Core->refs.loadRelaxed());
}
if (d_3_0_Core) {
d_3_0_Core->refs.deref();
- Q_ASSERT(d_3_0_Core->refs.load());
+ Q_ASSERT(d_3_0_Core->refs.loadRelaxed());
}
if (d_3_1_Core) {
d_3_1_Core->refs.deref();
- Q_ASSERT(d_3_1_Core->refs.load());
+ Q_ASSERT(d_3_1_Core->refs.loadRelaxed());
}
if (d_3_2_Core) {
d_3_2_Core->refs.deref();
- Q_ASSERT(d_3_2_Core->refs.load());
+ Q_ASSERT(d_3_2_Core->refs.loadRelaxed());
}
if (d_3_3_Core) {
d_3_3_Core->refs.deref();
- Q_ASSERT(d_3_3_Core->refs.load());
+ Q_ASSERT(d_3_3_Core->refs.loadRelaxed());
}
if (d_4_0_Core) {
d_4_0_Core->refs.deref();
- Q_ASSERT(d_4_0_Core->refs.load());
+ Q_ASSERT(d_4_0_Core->refs.loadRelaxed());
}
if (d_4_1_Core) {
d_4_1_Core->refs.deref();
- Q_ASSERT(d_4_1_Core->refs.load());
+ Q_ASSERT(d_4_1_Core->refs.loadRelaxed());
}
if (d_4_2_Core) {
d_4_2_Core->refs.deref();
- Q_ASSERT(d_4_2_Core->refs.load());
+ Q_ASSERT(d_4_2_Core->refs.loadRelaxed());
}
if (d_4_3_Core) {
d_4_3_Core->refs.deref();
- Q_ASSERT(d_4_3_Core->refs.load());
+ Q_ASSERT(d_4_3_Core->refs.loadRelaxed());
}
if (d_4_4_Core) {
d_4_4_Core->refs.deref();
- Q_ASSERT(d_4_4_Core->refs.load());
+ Q_ASSERT(d_4_4_Core->refs.loadRelaxed());
}
if (d_4_5_Core) {
d_4_5_Core->refs.deref();
- Q_ASSERT(d_4_5_Core->refs.load());
+ Q_ASSERT(d_4_5_Core->refs.loadRelaxed());
}
}
diff --git a/src/gui/opengl/qopenglpaintengine.cpp b/src/gui/opengl/qopenglpaintengine.cpp
index 042b9ebd79..c087326068 100644
--- a/src/gui/opengl/qopenglpaintengine.cpp
+++ b/src/gui/opengl/qopenglpaintengine.cpp
@@ -1475,7 +1475,10 @@ void QOpenGL2PaintEngineEx::renderHintsChanged()
if (!QOpenGLContext::currentContext()->isOpenGLES()) {
Q_D(QOpenGL2PaintEngineEx);
if ((state()->renderHints & QPainter::Antialiasing)
- || (state()->renderHints & QPainter::HighQualityAntialiasing))
+#if QT_DEPRECATED_SINCE(5, 14)
+ || (state()->renderHints & QPainter::HighQualityAntialiasing)
+#endif
+ )
d->funcs.glEnable(GL_MULTISAMPLE);
else
d->funcs.glDisable(GL_MULTISAMPLE);
diff --git a/src/gui/opengl/qopengltextureblitter.cpp b/src/gui/opengl/qopengltextureblitter.cpp
index b65df9dc82..b709f2f639 100644
--- a/src/gui/opengl/qopengltextureblitter.cpp
+++ b/src/gui/opengl/qopengltextureblitter.cpp
@@ -349,6 +349,9 @@ bool QOpenGLTextureBlitterPrivate::buildProgram(ProgramIndex idx, const char *vs
p->glProgram->setUniformValue(p->swizzleUniformPos, false);
+ // minmize state left set after a create()
+ p->glProgram->release();
+
return true;
}
diff --git a/src/gui/painting/qbrush.cpp b/src/gui/painting/qbrush.cpp
index 21e41979af..abb3268dfa 100644
--- a/src/gui/painting/qbrush.cpp
+++ b/src/gui/painting/qbrush.cpp
@@ -352,7 +352,7 @@ public:
QBrushData *brush;
QNullBrushData() : brush(new QBrushData)
{
- brush->ref.store(1);
+ brush->ref.storeRelaxed(1);
brush->style = Qt::BrushStyle(0);
brush->color = Qt::black;
}
@@ -411,7 +411,7 @@ void QBrush::init(const QColor &color, Qt::BrushStyle style)
d.reset(new QBrushData);
break;
}
- d->ref.store(1);
+ d->ref.storeRelaxed(1);
d->style = style;
d->color = color;
}
@@ -585,7 +585,7 @@ static Q_DECL_CONSTEXPR inline bool use_same_brushdata(Qt::BrushStyle lhs, Qt::B
void QBrush::detach(Qt::BrushStyle newStyle)
{
- if (use_same_brushdata(newStyle, d->style) && d->ref.load() == 1) {
+ if (use_same_brushdata(newStyle, d->style) && d->ref.loadRelaxed() == 1) {
d->style = newStyle;
return;
}
@@ -625,7 +625,7 @@ void QBrush::detach(Qt::BrushStyle newStyle)
x.reset(new QBrushData);
break;
}
- x->ref.store(1); // must be first lest the QBrushDataPointerDeleter turns into a no-op
+ x->ref.storeRelaxed(1); // must be first lest the QBrushDataPointerDeleter turns into a no-op
x->style = newStyle;
x->color = d->color;
x->transform = d->transform;
diff --git a/src/gui/painting/qbrush.h b/src/gui/painting/qbrush.h
index ca51430cf4..6a4ffab1c5 100644
--- a/src/gui/painting/qbrush.h
+++ b/src/gui/painting/qbrush.h
@@ -159,7 +159,7 @@ inline Qt::BrushStyle QBrush::style() const { return d->style; }
inline const QColor &QBrush::color() const { return d->color; }
inline const QMatrix &QBrush::matrix() const { return d->transform.toAffine(); }
inline QTransform QBrush::transform() const { return d->transform; }
-inline bool QBrush::isDetached() const { return d->ref.load() == 1; }
+inline bool QBrush::isDetached() const { return d->ref.loadRelaxed() == 1; }
/*******************************************************************************
@@ -371,6 +371,8 @@ public:
GagarinView = 178,
FabledSunset = 179,
PerfectBlue = 180,
+
+ NumPresets
};
Q_ENUM(Preset)
diff --git a/src/gui/painting/qcolor.cpp b/src/gui/painting/qcolor.cpp
index 174350d884..6cbc30e79a 100644
--- a/src/gui/painting/qcolor.cpp
+++ b/src/gui/painting/qcolor.cpp
@@ -1349,7 +1349,7 @@ void QColor::setRgbF(qreal r, qreal g, qreal b, qreal a)
*/
void QColor::setRgb(int r, int g, int b, int a)
{
- if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255 || (uint)a > 255) {
+ if (!isRgbaValid(r, g, b, a)) {
qWarning("QColor::setRgb: RGB parameters out of range");
invalidate();
return;
@@ -2398,10 +2398,7 @@ QColor QColor::fromRgba(QRgb rgba) noexcept
*/
QColor QColor::fromRgb(int r, int g, int b, int a)
{
- if (r < 0 || r > 255
- || g < 0 || g > 255
- || b < 0 || b > 255
- || a < 0 || a > 255) {
+ if (!isRgbaValid(r, g, b, a)) {
qWarning("QColor::fromRgb: RGB parameters out of range");
return QColor();
}
diff --git a/src/gui/painting/qcolor.h b/src/gui/painting/qcolor.h
index e3c267f97d..723b9fce73 100644
--- a/src/gui/painting/qcolor.h
+++ b/src/gui/painting/qcolor.h
@@ -67,9 +67,16 @@ public:
enum Spec { Invalid, Rgb, Hsv, Cmyk, Hsl, ExtendedRgb };
enum NameFormat { HexRgb, HexArgb };
- inline QColor() noexcept;
+ Q_DECL_CONSTEXPR QColor() noexcept
+ : cspec(Invalid), ct(USHRT_MAX, 0, 0, 0, 0) {}
QColor(Qt::GlobalColor color) noexcept;
- inline QColor(int r, int g, int b, int a = 255);
+ Q_DECL_CONSTEXPR QColor(int r, int g, int b, int a = 255) noexcept
+ : cspec(isRgbaValid(r, g, b, a) ? Rgb : Invalid),
+ ct(cspec == Rgb ? a * 0x0101 : 0,
+ cspec == Rgb ? r * 0x0101 : 0,
+ cspec == Rgb ? g * 0x0101 : 0,
+ cspec == Rgb ? b * 0x0101 : 0,
+ 0) {}
QColor(QRgb rgb) noexcept;
QColor(QRgba64 rgba64) noexcept;
#if QT_STRINGVIEW_LEVEL < 2
@@ -81,8 +88,11 @@ public:
QColor(Spec spec) noexcept;
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
- inline QColor(const QColor &color) noexcept; // ### Qt 6: remove all of these, the trivial ones are fine.
- QColor(QColor &&other) noexcept : cspec(other.cspec), ct(other.ct) {}
+ // ### Qt 6: remove all of these, the trivial ones are fine.
+ Q_DECL_CONSTEXPR QColor(const QColor &color) noexcept
+ : cspec(color.cspec), ct(color.ct)
+ {}
+ Q_DECL_CONSTEXPR QColor(QColor &&other) noexcept : cspec(other.cspec), ct(other.ct) {}
QColor &operator=(QColor &&other) noexcept
{ cspec = other.cspec; ct = other.ct; return *this; }
QColor &operator=(const QColor &) noexcept;
@@ -244,8 +254,18 @@ private:
template <typename String>
bool setColorFromString(String name);
+ static Q_DECL_CONSTEXPR bool isRgbaValid(int r, int g, int b, int a = 255) noexcept Q_DECL_CONST_FUNCTION
+ {
+ return uint(r) <= 255 && uint(g) <= 255 && uint(b) <= 255 && uint(a) <= 255;
+ }
+
Spec cspec;
- union {
+ union CT {
+#ifdef Q_COMPILER_UNIFORM_INIT
+ CT() {} // doesn't init anything, thus can't be constexpr
+ Q_DECL_CONSTEXPR explicit CT(ushort a1, ushort a2, ushort a3, ushort a4, ushort a5) noexcept
+ : array{a1, a2, a3, a4, a5} {}
+#endif
struct {
ushort alpha;
ushort red;
@@ -292,12 +312,6 @@ private:
};
Q_DECLARE_TYPEINFO(QColor, QT_VERSION >= QT_VERSION_CHECK(6,0,0) ? Q_MOVABLE_TYPE : Q_RELOCATABLE_TYPE);
-inline QColor::QColor() noexcept
-{ invalidate(); }
-
-inline QColor::QColor(int r, int g, int b, int a)
-{ setRgb(r, g, b, a); }
-
inline QColor::QColor(QLatin1String aname)
{ setNamedColor(aname); }
@@ -309,12 +323,6 @@ inline QColor::QColor(const QString& aname)
{ setNamedColor(aname); }
#endif
-#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
-inline QColor::QColor(const QColor &acolor) noexcept
- : cspec(acolor.cspec)
-{ ct.argb = acolor.ct.argb; }
-#endif
-
inline bool QColor::isValid() const noexcept
{ return cspec != Invalid; }
diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h
index a49c46f195..95e0655d0c 100644
--- a/src/gui/painting/qcolorspace_p.h
+++ b/src/gui/painting/qcolorspace_p.h
@@ -123,7 +123,7 @@ public:
table[0] = other.table[0];
table[1] = other.table[1];
table[2] = other.table[2];
- generated.store(1);
+ generated.storeRelaxed(1);
}
}
QSharedPointer<QColorTrcLut> &operator[](int i) { return table[i]; }
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp
index 2f81449693..de08bf4221 100644
--- a/src/gui/painting/qcolortransform.cpp
+++ b/src/gui/painting/qcolortransform.cpp
@@ -71,7 +71,7 @@ void QColorTransformPrivate::updateLutsIn() const
if (colorSpaceIn->lut.generated.loadAcquire())
return;
QMutexLocker lock(&QColorSpacePrivate::s_lutWriteLock);
- if (colorSpaceIn->lut.generated.load())
+ if (colorSpaceIn->lut.generated.loadRelaxed())
return;
for (int i = 0; i < 3; ++i) {
@@ -96,7 +96,7 @@ void QColorTransformPrivate::updateLutsOut() const
if (colorSpaceOut->lut.generated.loadAcquire())
return;
QMutexLocker lock(&QColorSpacePrivate::s_lutWriteLock);
- if (colorSpaceOut->lut.generated.load())
+ if (colorSpaceOut->lut.generated.loadRelaxed())
return;
for (int i = 0; i < 3; ++i) {
if (!colorSpaceOut->trc[i].isValid())
diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp
index ac15d8f4f4..006befea22 100644
--- a/src/gui/painting/qdrawhelper.cpp
+++ b/src/gui/painting/qdrawhelper.cpp
@@ -4566,7 +4566,9 @@ static void blend_color_rgb16(int count, const QSpan *spans, void *userData)
if (mode == QPainter::CompositionMode_Source) {
// inline for performance
ushort c = data->solidColor.toRgb16();
- while (count--) {
+ for (; count--; spans++) {
+ if (!spans->len)
+ continue;
ushort *target = ((ushort *)data->rasterBuffer->scanLine(spans->y)) + spans->x;
if (spans->coverage == 255) {
qt_memfill(target, c, spans->len);
@@ -4579,13 +4581,14 @@ static void blend_color_rgb16(int count, const QSpan *spans, void *userData)
++target;
}
}
- ++spans;
}
return;
}
if (mode == QPainter::CompositionMode_SourceOver) {
- while (count--) {
+ for (; count--; spans++) {
+ if (!spans->len)
+ continue;
uint color = BYTE_MUL(data->solidColor.toArgb32(), spans->coverage);
int ialpha = qAlpha(~color);
ushort c = qConvertRgb32To16(color);
@@ -4617,7 +4620,6 @@ static void blend_color_rgb16(int count, const QSpan *spans, void *userData)
// one last pixel beyond a full word
*target = c + BYTE_MUL_RGB16(*target, ialpha);
}
- ++spans;
}
return;
}
@@ -4634,6 +4636,11 @@ void handleSpans(int count, const QSpan *spans, const QSpanData *data, T &handle
int coverage = 0;
while (count) {
+ if (!spans->len) {
+ ++spans;
+ --count;
+ continue;
+ }
int x = spans->x;
const int y = spans->y;
int right = x + spans->len;
@@ -4790,7 +4797,9 @@ static void blend_untransformed_generic(int count, const QSpan *spans, void *use
int xoff = -qRound(-data->dx);
int yoff = -qRound(-data->dy);
- while (count--) {
+ for (; count--; spans++) {
+ if (!spans->len)
+ continue;
int x = spans->x;
int length = spans->len;
int sx = xoff + x;
@@ -4818,7 +4827,6 @@ static void blend_untransformed_generic(int count, const QSpan *spans, void *use
}
}
}
- ++spans;
}
}
@@ -4840,7 +4848,9 @@ static void blend_untransformed_generic_rgb64(int count, const QSpan *spans, voi
int xoff = -qRound(-data->dx);
int yoff = -qRound(-data->dy);
- while (count--) {
+ for (; count--; spans++) {
+ if (!spans->len)
+ continue;
int x = spans->x;
int length = spans->len;
int sx = xoff + x;
@@ -4868,7 +4878,6 @@ static void blend_untransformed_generic_rgb64(int count, const QSpan *spans, voi
}
}
}
- ++spans;
}
}
#endif
@@ -4889,7 +4898,9 @@ static void blend_untransformed_argb(int count, const QSpan *spans, void *userDa
int xoff = -qRound(-data->dx);
int yoff = -qRound(-data->dy);
- while (count--) {
+ for (; count--; spans++) {
+ if (!spans->len)
+ continue;
int x = spans->x;
int length = spans->len;
int sx = xoff + x;
@@ -4909,7 +4920,6 @@ static void blend_untransformed_argb(int count, const QSpan *spans, void *userDa
op.func(dest, src, length, coverage);
}
}
- ++spans;
}
}
@@ -4982,7 +4992,12 @@ static void blend_untransformed_rgb565(int count, const QSpan *spans, void *user
int xoff = -qRound(-data->dx);
int yoff = -qRound(-data->dy);
- while (count--) {
+ const QSpan *end = spans + count;
+ while (spans < end) {
+ if (!spans->len) {
+ ++spans;
+ continue;
+ }
const quint8 coverage = (data->texture.const_alpha * spans->coverage) >> 8;
if (coverage == 0) {
++spans;
diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp
index 85ddff53db..461ad51200 100644
--- a/src/gui/painting/qpaintengine_raster.cpp
+++ b/src/gui/painting/qpaintengine_raster.cpp
@@ -904,7 +904,11 @@ void QRasterPaintEngine::renderHintsChanged()
bool was_aa = s->flags.antialiased;
bool was_bilinear = s->flags.bilinear;
- s->flags.antialiased = bool(s->renderHints & (QPainter::Antialiasing | QPainter::HighQualityAntialiasing));
+ s->flags.antialiased = bool(s->renderHints & QPainter::Antialiasing);
+#if QT_DEPRECATED_SINCE(5, 14)
+ if (s->renderHints & QPainter::HighQualityAntialiasing)
+ s->flags.antialiased = true;
+#endif
s->flags.bilinear = bool(s->renderHints & QPainter::SmoothPixmapTransform);
s->flags.legacy_rounding = !bool(s->renderHints & QPainter::Antialiasing) && bool(s->renderHints & QPainter::Qt4CompatiblePainting);
@@ -4149,7 +4153,7 @@ static void qt_span_fill_clipped(int spanCount, const QSpan *spans, void *userDa
Clip spans to \a{clip}-rectangle.
Returns number of unclipped spans
*/
-static int qt_intersect_spans(QT_FT_Span *spans, int numSpans,
+static int qt_intersect_spans(QT_FT_Span *&spans, int numSpans,
const QRect &clip)
{
const short minx = clip.left();
@@ -4157,29 +4161,32 @@ static int qt_intersect_spans(QT_FT_Span *spans, int numSpans,
const short maxx = clip.right();
const short maxy = clip.bottom();
- int n = 0;
- for (int i = 0; i < numSpans; ++i) {
- if (spans[i].y > maxy)
+ QT_FT_Span *end = spans + numSpans;
+ while (spans < end) {
+ if (spans->y >= miny)
+ break;
+ ++spans;
+ }
+
+ QT_FT_Span *s = spans;
+ while (s < end) {
+ if (s->y > maxy)
break;
- if (spans[i].y < miny
- || spans[i].x > maxx
- || spans[i].x + spans[i].len <= minx) {
+ if (s->x > maxx || s->x + s->len <= minx) {
+ s->len = 0;
+ ++s;
continue;
}
- if (spans[i].x < minx) {
- spans[n].len = qMin(spans[i].len - (minx - spans[i].x), maxx - minx + 1);
- spans[n].x = minx;
+ if (s->x < minx) {
+ s->len = qMin(s->len - (minx - s->x), maxx - minx + 1);
+ s->x = minx;
} else {
- spans[n].x = spans[i].x;
- spans[n].len = qMin(spans[i].len, ushort(maxx - spans[n].x + 1));
+ s->len = qMin(s->len, ushort(maxx - s->x + 1));
}
- if (spans[n].len == 0)
- continue;
- spans[n].y = spans[i].y;
- spans[n].coverage = spans[i].coverage;
- ++n;
+ ++s;
}
- return n;
+
+ return s - spans;
}
@@ -4192,11 +4199,12 @@ static void qt_span_fill_clipRect(int count, const QSpan *spans,
Q_ASSERT(fillData->clip);
Q_ASSERT(!fillData->clip->clipRect.isEmpty());
+ QSpan *s = const_cast<QSpan *>(spans);
// hw: check if this const_cast<> is safe!!!
- count = qt_intersect_spans(const_cast<QSpan*>(spans), count,
+ count = qt_intersect_spans(s, count,
fillData->clip->clipRect);
if (count > 0)
- fillData->unclipped_blend(count, spans, fillData);
+ fillData->unclipped_blend(count, s, fillData);
}
static void qt_span_clip(int count, const QSpan *spans, void *userData)
@@ -4773,7 +4781,8 @@ static inline void drawEllipsePoints(int x, int y, int length,
if (length == 0)
return;
- QT_FT_Span outline[4];
+ QT_FT_Span _outline[4];
+ QT_FT_Span *outline = _outline;
const int midx = rect.x() + (rect.width() + 1) / 2;
const int midy = rect.y() + (rect.height() + 1) / 2;
@@ -4805,7 +4814,8 @@ static inline void drawEllipsePoints(int x, int y, int length,
outline[3].coverage = 255;
if (brush_func && outline[0].x + outline[0].len < outline[1].x) {
- QT_FT_Span fill[2];
+ QT_FT_Span _fill[2];
+ QT_FT_Span *fill = _fill;
// top fill
fill[0].x = outline[0].x + outline[0].len - 1;
diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp
index 95e6bda78b..84b34e390b 100644
--- a/src/gui/painting/qpainter.cpp
+++ b/src/gui/painting/qpainter.cpp
@@ -283,7 +283,7 @@ bool QPainterPrivate::attachPainterPrivate(QPainter *q, QPaintDevice *pdev)
Q_ASSERT(q->d_ptr->state);
// Now initialize the painter with correct widget properties.
- q->initFrom(pdev);
+ q->d_ptr->initFrom(pdev);
QPoint offset;
pdev->redirected(&offset);
offset += q->d_ptr->engine->coordinateOffset();
@@ -1560,22 +1560,28 @@ void QPainter::initFrom(const QPaintDevice *device)
{
Q_ASSERT_X(device, "QPainter::initFrom(const QPaintDevice *device)", "QPaintDevice cannot be 0");
Q_D(QPainter);
- if (!d->engine) {
+ d->initFrom(device);
+}
+#endif
+
+void QPainterPrivate::initFrom(const QPaintDevice *device)
+{
+ if (!engine) {
qWarning("QPainter::initFrom: Painter not active, aborted");
return;
}
- device->initPainter(this);
+ Q_Q(QPainter);
+ device->initPainter(q);
- if (d->extended) {
- d->extended->penChanged();
- } else if (d->engine) {
- d->engine->setDirty(QPaintEngine::DirtyPen);
- d->engine->setDirty(QPaintEngine::DirtyBrush);
- d->engine->setDirty(QPaintEngine::DirtyFont);
+ if (extended) {
+ extended->penChanged();
+ } else if (engine) {
+ engine->setDirty(QPaintEngine::DirtyPen);
+ engine->setDirty(QPaintEngine::DirtyBrush);
+ engine->setDirty(QPaintEngine::DirtyFont);
}
}
-#endif
/*!
Saves the current painter state (pushes the state onto a stack). A
@@ -1843,7 +1849,7 @@ bool QPainter::begin(QPaintDevice *pd)
// Copy painter properties from original paint device,
// required for QPixmap::grabWidget()
if (d->original_device->devType() == QInternal::Widget) {
- initFrom(d->original_device);
+ d->initFrom(d->original_device);
} else {
d->state->layoutDirection = Qt::LayoutDirectionAuto;
// make sure we have a font compatible with the paintdevice
diff --git a/src/gui/painting/qpainter.h b/src/gui/painting/qpainter.h
index 843f24e3e1..3394da63c7 100644
--- a/src/gui/painting/qpainter.h
+++ b/src/gui/painting/qpainter.h
@@ -89,8 +89,10 @@ public:
Antialiasing = 0x01,
TextAntialiasing = 0x02,
SmoothPixmapTransform = 0x04,
- HighQualityAntialiasing = 0x08,
- NonCosmeticDefaultPen = 0x10,
+#if QT_DEPRECATED_SINCE(5, 14)
+ HighQualityAntialiasing Q_DECL_ENUMERATOR_DEPRECATED_X("Use Antialiasing instead") = 0x08,
+ NonCosmeticDefaultPen Q_DECL_ENUMERATOR_DEPRECATED_X("Default pen is non-cosmetic now") = 0x10,
+#endif
Qt4CompatiblePainting = 0x20,
LosslessImageRendering = 0x40,
};
diff --git a/src/gui/painting/qpainter_p.h b/src/gui/painting/qpainter_p.h
index bd2bc4c9b3..29d4880eb9 100644
--- a/src/gui/painting/qpainter_p.h
+++ b/src/gui/painting/qpainter_p.h
@@ -256,6 +256,7 @@ public:
QTransform hidpiScaleTransform() const;
static bool attachPainterPrivate(QPainter *q, QPaintDevice *pdev);
void detachPainterPrivate(QPainter *q);
+ void initFrom(const QPaintDevice *device);
QPaintDevice *device;
QPaintDevice *original_device;
diff --git a/src/gui/painting/qpainterpath.cpp b/src/gui/painting/qpainterpath.cpp
index 42872359d7..8801e66b0f 100644
--- a/src/gui/painting/qpainterpath.cpp
+++ b/src/gui/painting/qpainterpath.cpp
@@ -71,6 +71,24 @@
QT_BEGIN_NAMESPACE
+static inline bool isValidCoord(qreal c)
+{
+ if (sizeof(qreal) >= sizeof(double))
+ return qIsFinite(c) && fabs(c) < 1e128;
+ else
+ return qIsFinite(c) && fabsf(float(c)) < 1e16f;
+}
+
+static bool hasValidCoords(QPointF p)
+{
+ return isValidCoord(p.x()) && isValidCoord(p.y());
+}
+
+static bool hasValidCoords(QRectF r)
+{
+ return isValidCoord(r.x()) && isValidCoord(r.y()) && isValidCoord(r.width()) && isValidCoord(r.height());
+}
+
struct QPainterPathPrivateDeleter
{
static inline void cleanup(QPainterPathPrivate *d)
@@ -560,7 +578,7 @@ QPainterPath::QPainterPath(const QPointF &startPoint)
void QPainterPath::detach()
{
- if (d_ptr->ref.load() != 1)
+ if (d_ptr->ref.loadRelaxed() != 1)
detach_helper();
setDirty(true);
}
@@ -657,8 +675,9 @@ void QPainterPath::reserve(int size)
{
Q_D(QPainterPath);
if ((!d && size > 0) || (d && d->elements.capacity() < size)) {
+ ensureData();
detach();
- d->elements.reserve(size);
+ d_func()->elements.reserve(size);
}
}
@@ -724,9 +743,9 @@ void QPainterPath::moveTo(const QPointF &p)
printf("QPainterPath::moveTo() (%.2f,%.2f)\n", p.x(), p.y());
#endif
- if (!qt_is_finite(p.x()) || !qt_is_finite(p.y())) {
+ if (!hasValidCoords(p)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::moveTo: Adding point where x or y is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::moveTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -774,9 +793,9 @@ void QPainterPath::lineTo(const QPointF &p)
printf("QPainterPath::lineTo() (%.2f,%.2f)\n", p.x(), p.y());
#endif
- if (!qt_is_finite(p.x()) || !qt_is_finite(p.y())) {
+ if (!hasValidCoords(p)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::lineTo: Adding point where x or y is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::lineTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -833,10 +852,9 @@ void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &
c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y());
#endif
- if (!qt_is_finite(c1.x()) || !qt_is_finite(c1.y()) || !qt_is_finite(c2.x()) || !qt_is_finite(c2.y())
- || !qt_is_finite(e.x()) || !qt_is_finite(e.y())) {
+ if (!hasValidCoords(c1) || !hasValidCoords(c2) || !hasValidCoords(e)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::cubicTo: Adding point where x or y is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::cubicTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -890,9 +908,9 @@ void QPainterPath::quadTo(const QPointF &c, const QPointF &e)
c.x(), c.y(), e.x(), e.y());
#endif
- if (!qt_is_finite(c.x()) || !qt_is_finite(c.y()) || !qt_is_finite(e.x()) || !qt_is_finite(e.y())) {
+ if (!hasValidCoords(c) || !hasValidCoords(e)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::quadTo: Adding point where x or y is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::quadTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -961,10 +979,9 @@ void QPainterPath::arcTo(const QRectF &rect, qreal startAngle, qreal sweepLength
rect.x(), rect.y(), rect.width(), rect.height(), startAngle, sweepLength);
#endif
- if ((!qt_is_finite(rect.x()) && !qt_is_finite(rect.y())) || !qt_is_finite(rect.width()) || !qt_is_finite(rect.height())
- || !qt_is_finite(startAngle) || !qt_is_finite(sweepLength)) {
+ if (!hasValidCoords(rect) || !isValidCoord(startAngle) || !isValidCoord(sweepLength)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::arcTo: Adding arc where a parameter is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::arcTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -1067,9 +1084,9 @@ QPointF QPainterPath::currentPosition() const
*/
void QPainterPath::addRect(const QRectF &r)
{
- if (!qt_is_finite(r.x()) || !qt_is_finite(r.y()) || !qt_is_finite(r.width()) || !qt_is_finite(r.height())) {
+ if (!hasValidCoords(r)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::addRect: Adding rect where a parameter is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::addRect: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -1147,10 +1164,9 @@ void QPainterPath::addPolygon(const QPolygonF &polygon)
*/
void QPainterPath::addEllipse(const QRectF &boundingRect)
{
- if (!qt_is_finite(boundingRect.x()) || !qt_is_finite(boundingRect.y())
- || !qt_is_finite(boundingRect.width()) || !qt_is_finite(boundingRect.height())) {
+ if (!hasValidCoords(boundingRect)) {
#ifndef QT_NO_DEBUG
- qWarning("QPainterPath::addEllipse: Adding ellipse where a parameter is NaN or Inf, ignoring call");
+ qWarning("QPainterPath::addEllipse: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
@@ -2501,6 +2517,7 @@ QDataStream &operator<<(QDataStream &s, const QPainterPath &p)
*/
QDataStream &operator>>(QDataStream &s, QPainterPath &p)
{
+ bool errorDetected = false;
int size;
s >> size;
@@ -2519,10 +2536,11 @@ QDataStream &operator>>(QDataStream &s, QPainterPath &p)
s >> x;
s >> y;
Q_ASSERT(type >= 0 && type <= 3);
- if (!qt_is_finite(x) || !qt_is_finite(y)) {
+ if (!isValidCoord(qreal(x)) || !isValidCoord(qreal(y))) {
#ifndef QT_NO_DEBUG
- qWarning("QDataStream::operator>>: NaN or Inf element found in path, skipping it");
+ qWarning("QDataStream::operator>>: Invalid QPainterPath coordinates read, skipping it");
#endif
+ errorDetected = true;
continue;
}
QPainterPath::Element elm = { qreal(x), qreal(y), QPainterPath::ElementType(type) };
@@ -2535,6 +2553,8 @@ QDataStream &operator>>(QDataStream &s, QPainterPath &p)
p.d_func()->fillRule = Qt::FillRule(fillRule);
p.d_func()->dirtyBounds = true;
p.d_func()->dirtyControlBounds = true;
+ if (errorDetected)
+ p = QPainterPath(); // Better than to return path with possibly corrupt datastructure, which would likely cause crash
return s;
}
#endif // QT_NO_DATASTREAM
diff --git a/src/gui/painting/qpainterpath_p.h b/src/gui/painting/qpainterpath_p.h
index 4eb541ec65..22bdbde2a9 100644
--- a/src/gui/painting/qpainterpath_p.h
+++ b/src/gui/painting/qpainterpath_p.h
@@ -281,7 +281,7 @@ inline bool QPainterPathData::isClosed() const
inline void QPainterPathData::close()
{
- Q_ASSERT(ref.load() == 1);
+ Q_ASSERT(ref.loadRelaxed() == 1);
require_moveTo = true;
const QPainterPath::Element &first = elements.at(cStart);
QPainterPath::Element &last = elements.last();
@@ -308,7 +308,7 @@ inline void QPainterPathData::maybeMoveTo()
inline void QPainterPathData::clear()
{
- Q_ASSERT(ref.load() == 1);
+ Q_ASSERT(ref.loadRelaxed() == 1);
elements.clear();
diff --git a/src/gui/painting/qpen.cpp b/src/gui/painting/qpen.cpp
index 58a1716037..dc6e3e04d0 100644
--- a/src/gui/painting/qpen.cpp
+++ b/src/gui/painting/qpen.cpp
@@ -363,13 +363,13 @@ QPen::~QPen()
void QPen::detach()
{
- if (d->ref.load() == 1)
+ if (d->ref.loadRelaxed() == 1)
return;
QPenData *x = new QPenData(*static_cast<QPenData *>(d));
if (!d->ref.deref())
delete d;
- x->ref.store(1);
+ x->ref.storeRelaxed(1);
d = x;
}
@@ -885,7 +885,7 @@ bool QPen::operator==(const QPen &p) const
bool QPen::isDetached()
{
- return d->ref.load() == 1;
+ return d->ref.loadRelaxed() == 1;
}
diff --git a/src/gui/painting/qstroker.cpp b/src/gui/painting/qstroker.cpp
index 5b6990e667..271d3ba6bf 100644
--- a/src/gui/painting/qstroker.cpp
+++ b/src/gui/painting/qstroker.cpp
@@ -522,7 +522,7 @@ void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine
QLineF shortCut(prevLine.p2(), nextLine.p1());
qreal angle = shortCut.angleTo(prevLine);
- if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
+ if ((type == QLineF::BoundedIntersection || (angle > qreal(90.01))) && nextLine.length() > offset) {
emitLineTo(focal_x, focal_y);
emitLineTo(qt_real_to_fixed(nextLine.x1()), qt_real_to_fixed(nextLine.y1()));
return;
diff --git a/src/gui/painting/qtextureglyphcache.cpp b/src/gui/painting/qtextureglyphcache.cpp
index c21f2cdda4..e8c47df21c 100644
--- a/src/gui/painting/qtextureglyphcache.cpp
+++ b/src/gui/painting/qtextureglyphcache.cpp
@@ -285,6 +285,8 @@ QImageTextureGlyphCache::~QImageTextureGlyphCache()
void QImageTextureGlyphCache::resizeTextureData(int width, int height)
{
m_image = m_image.copy(0, 0, width, height);
+ // Regions not part of the copy are initialized to 0, and that is just what
+ // we need.
}
void QImageTextureGlyphCache::createTextureData(int width, int height)
@@ -305,6 +307,12 @@ void QImageTextureGlyphCache::createTextureData(int width, int height)
default:
Q_UNREACHABLE();
}
+
+ // Regions not touched by the glyphs must be initialized to 0. (such
+ // locations may in fact be sampled with styled (shifted) text materials)
+ // When resizing, the QImage copy() does this implicitly but the initial
+ // contents must be zeroed out explicitly here.
+ m_image.fill(0);
}
void QImageTextureGlyphCache::fillTexture(const Coord &c, glyph_t g, QFixed subPixelPosition)
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
new file mode 100644
index 0000000000..d7c1607e57
--- /dev/null
+++ b/src/gui/rhi/qrhi.cpp
@@ -0,0 +1,5334 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhi_p_p.h"
+#include <qmath.h>
+
+#include "qrhinull_p_p.h"
+#ifndef QT_NO_OPENGL
+#include "qrhigles2_p_p.h"
+#endif
+#if QT_CONFIG(vulkan)
+#include "qrhivulkan_p_p.h"
+#endif
+#ifdef Q_OS_WIN
+#include "qrhid3d11_p_p.h"
+#endif
+//#ifdef Q_OS_DARWIN
+#ifdef Q_OS_MACOS
+#include "qrhimetal_p_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhi
+ \inmodule QtRhi
+
+ \brief Accelerated 2D/3D graphics API abstraction.
+
+ The Qt Rendering Hardware Interface is an abstraction for hardware accelerated
+ graphics APIs, such as, \l{https://www.khronos.org/opengl/}{OpenGL},
+ \l{https://www.khronos.org/opengles/}{OpenGL ES},
+ \l{https://docs.microsoft.com/en-us/windows/desktop/direct3d}{Direct3D},
+ \l{https://developer.apple.com/metal/}{Metal}, and
+ \l{https://www.khronos.org/vulkan/}{Vulkan}.
+
+ Some of the main design goals are:
+
+ \list
+
+ \li Simple, minimal, understandable, extensible. Follow the proven path of the
+ Qt Quick scenegraph.
+
+ \li Aim to be a product - and in the bigger picture, part of a product (Qt) -
+ that is usable out of the box both by internal (such as, Qt Quick) and,
+ eventually, external users.
+
+ \li Not a complete 1:1 wrapper for any of the underlying APIs. The feature set
+ is tuned towards the needs of Qt's 2D and 3D offering (QPainter, Qt Quick, Qt
+ 3D Studio). Iterate and evolve in a sustainable manner.
+
+ \li Intrinsically cross-platform, without reinventing: abstracting
+ cross-platform aspects of certain APIs (such as, OpenGL context creation and
+ windowing system interfaces, Vulkan instance and surface management) is not in
+ scope here. These are delegated to the existing QtGui facilities (QWindow,
+ QOpenGLContext, QVulkanInstance) and its backing QPA architecture.
+
+ \endlist
+
+ Each QRhi instance is backed by a backend for a specific graphics API. The
+ selection of the backend is a run time choice and is up to the application
+ or library that creates the QRhi instance. Some backends are available on
+ multiple platforms (OpenGL, Vulkan, Null), while APIs specific to a given
+ platform are only available when running on the platform in question (Metal
+ on macOS/iOS/tvOS, Direct3D on Windows).
+
+ The available backends currently are:
+
+ \list
+
+ \li OpenGL 2.1 or OpenGL ES 2.0 or newer. Some extensions are utilized when
+ present, for example to enable multisample framebuffers.
+
+ \li Direct3D 11.1
+
+ \li Metal
+
+ \li Vulkan 1.0, optionally with some extensions that are part of Vulkan 1.1
+
+ \li Null - A "dummy" backend that issues no graphics calls at all.
+
+ \endlist
+
+ In order to allow shader code to be written once in Qt applications and
+ libraries, all shaders are expected to be written in a single language
+ which is then compiled into SPIR-V. Versions for various shading language
+ are then generated from that, together with reflection information (inputs,
+ outputs, shader resources). This is then packed into easily and efficiently
+ serializable QShader instances. The compilers and tools to generate such
+ shaders are not part of QRhi, but the core classes for using such shaders,
+ QShader and QShaderDescription, are.
+
+ \section2 Design Fundamentals
+
+ A QRhi cannot be instantiated directly. Instead, use the create()
+ function. Delete the QRhi instance normally to release the graphics device.
+
+ \section3 Resources
+
+ Instances of classes deriving from QRhiResource, such as, QRhiBuffer,
+ QRhiTexture, etc., encapsulate zero, one, or more native graphics
+ resources. Instances of such classes are always created via the \c new
+ functions of the QRhi, such as, newBuffer(), newTexture(),
+ newTextureRenderTarget(), newSwapChain().
+
+ \badcode
+ vbuf = rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
+ if (!vbuf->build()) { error }
+ ...
+ delete vbuf;
+ \endcode
+
+ \list
+
+ \li The returned value from both create() and functions like newBuffer() is
+ owned by the caller.
+
+ \li Just creating a QRhiResource subclass never allocates or initializes any
+ native resources. That is only done when calling the \c build function of a
+ subclass, for example, QRhiBuffer::build() or QRhiTexture::build().
+
+ \li The exception is
+ QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() and
+ QRhiSwapChain::newCompatibleRenderPassDescriptor(). There is no \c build
+ operation for these and the returned object is immediately active.
+
+ \li The resource objects themselves are treated as immutable: once a
+ resource is built, changing any parameters via the setters, such as,
+ QRhiTexture::setPixelSize(), has no effect, unless the underlying native
+ resource is released and \c build is called again. See more about resource
+ reuse in the sections below.
+
+ \li The underlying native resources are scheduled for releasing by the
+ QRhiResource destructor, or by calling QRhiResource::release(). Backends
+ often queue release requests and defer executing them to an unspecified
+ time, this is hidden from the applications. This way applications do not
+ have to worry about releasing native resources that may still be in use by
+ an in-flight frame.
+
+ \li Note that this does not mean that a QRhiResource can freely be
+ destroyed or release()'d within a frame (that is, in a
+ \l{QRhiCommandBuffer::beginFrame()}{beginFrame()} -
+ \l{QRhiCommandBuffer::endFrame()}{endFrame()} section). As a general rule,
+ all referenced QRhiResource objects must stay unchanged until the frame is
+ submitted by calling \l{QRhiCommandBuffer::endFrame()}{endFrame()}. To ease
+ this, QRhiResource::releaseAndDestroyLater() is provided as a convenience.
+
+ \endlist
+
+ \section3 Command buffers and deferred command execution
+
+ Regardless of the design and capabilities of the underlying graphics API,
+ all QRhi backends implement some level of command buffers. No
+ QRhiCommandBuffer function issues any native bind or draw command (such as,
+ \c glDrawElements) directly. Commands are always recorded in a queue,
+ either native or provided by the QRhi backend. The command buffer is
+ submitted, and so execution starts only upon QRhi::endFrame() or
+ QRhi::finish().
+
+ The deferred nature has consequences for some types of objects. For example,
+ writing to a dynamic buffer multiple times within a frame, in case such
+ buffers are backed by host-visible memory, will result in making the
+ results of all writes are visible to all draw calls in the command buffer
+ of the frame, regardless of when the dynamic buffer update was recorded
+ relative to a draw call.
+
+ Furthermore, instances of QRhiResource subclasses must be treated immutable
+ within a frame in which they are referenced in any way. Create or rebuild
+ all resources upfront, before starting to record commands for the next
+ frame. Reusing a QRhiResource instance within a frame (by rebuilding it and
+ then referencing it again in the same \c{beginFrame - endFrame} section)
+ should be avoided as it may lead to unexpected results, depending on the
+ backend.
+
+ As a general rule, all referenced QRhiResource objects must stay valid and
+ unmodified until the frame is submitted by calling
+ \l{QRhiCommandBuffer::endFrame()}{endFrame()}. On the other hand, calling
+ \l{QRhiResource::release()}{release()} or destroying the QRhiResource are
+ always safe once the frame is submitted, regardless of the status of the
+ underlying native resources (which may still be in use by the GPU - but
+ that is taken care of internally).
+
+ Unlike APIs like OpenGL, upload and copy type of commands cannot be mixed
+ with draw commands. The typical renderer will involve a sequence similar to
+ the following: \c{(re)build resources} - \c{begin frame} - \c{record
+ uploads and copies} - \c{start renderpass} - \c{record draw calls} - \c{end
+ renderpass} - \c{end frame}. Recording copy type of operations happens via
+ QRhiResourceUpdateBatch. Such operations are committed typically on
+ \l{QRhiCommandBuffer::beginPass()}{beginPass()}.
+
+ When working with legacy rendering engines designed for OpenGL, the
+ migration to QRhi often involves redesigning from having a single \c render
+ step (that performs copies and uploads, clears buffers, and issues draw
+ calls, all mixed together) to a clearly separated, two phase \c prepare -
+ \c render setup where the \c render step only starts a renderpass and
+ records draw calls, while all resource creation and queuing of updates,
+ uploads and copies happens beforehand, in the \c prepare step.
+
+ QRhi does not at the moment allow freely creating and submitting command
+ buffers. This may be lifted in the future to some extent, in particular if
+ compute support is introduced, but the model of well defined
+ \c{frame-start} and \c{frame-end} points, combined with a dedicated,
+ "frame" command buffer, where \c{frame-end} implies presenting, is going to
+ remain the primary way of operating since this is what fits Qt's various UI
+ technologies best.
+
+ \section3 Threading
+
+ A QRhi instance and the associated resources can be created and used on any
+ thread but all usage must be limited to that one single thread. When
+ rendering to multiple QWindows in an application, having a dedicated thread
+ and QRhi instance for each window is often advisable, as this can eliminate
+ issues with unexpected throttling caused by presenting to multiple windows.
+ Conceptually that is then the same as how Qt Quick scene graph's threaded
+ render loop operates when working directly with OpenGL: one thread for each
+ window, one QOpenGLContext for each thread. When moving onto QRhi,
+ QOpenGLContext is replaced by QRhi, making the migration straightforward.
+
+ When it comes to externally created native objects, such as OpenGL contexts
+ passed in via QRhiGles2NativeHandles, it is up to the application to ensure
+ they are not misused by other threads.
+
+ Resources are not shareable between QRhi instances. This is an intentional
+ choice since QRhi hides most queue, command buffer, and resource
+ synchronization related tasks, and provides no API for them. Safe and
+ efficient concurrent use of graphics resources from multiple threads is
+ tied to those concepts, however, and is thus a topic that is currently out
+ of scope, but may be introduced in the future.
+
+ \section3 Resource synchronization
+
+ QRhi does not expose APIs for resource barriers or image layout
+ transitions. Such synchronization is done implicitly by the backends, where
+ applicable (for example, Vulkan), by tracking resource usage as necessary.
+
+ \note Resources within a render or compute pass are expected to be bound to
+ a single usage during that pass. For example, a buffer can be used as
+ vertex, index, uniform, or storage buffer, but not a combination of them
+ within a single pass. However, it is perfectly fine to use a buffer as a
+ storage buffer in a compute pass, and then as a vertex buffer in a render
+ pass, for example, assuming the buffer declared both usages upon creation.
+
+ \note Textures have this rule relaxed in certain cases, because using two
+ subresources (typically two different mip levels) of the same texture for
+ different access (one for load, one for store) is supported even within the
+ same pass.
+
+ \section3 Resource reuse
+
+ From the user's point of view a QRhiResource is reusable immediately after
+ calling QRhiResource::release(). With the exception of swapchains, calling
+ \c build() on an already built object does an implicit \c release(). This
+ provides a handy shortcut to reuse a QRhiResource instance with different
+ parameters, with a new native graphics object underneath.
+
+ The importance of reusing the same object lies in the fact that some
+ objects reference other objects: for example, a QRhiShaderResourceBindings
+ can reference QRhiBuffer, QRhiTexture, and QRhiSampler instances. If in a
+ later frame one of these buffers need to be resized or a sampler parameter
+ needs changing, destroying and creating a whole new QRhiBuffer or
+ QRhiSampler would invalidate all references to the old instance. By just
+ changing the appropriate parameters via QRhiBuffer::setSize() or similar
+ and then calling QRhiBuffer::build(), everything works as expected and
+ there is no need to touch the QRhiShaderResourceBindings at all, even
+ though there is a good chance that under the hood the QRhiBuffer is now
+ backed by a whole new native buffer.
+
+ \badcode
+ ubuf = rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 256);
+ ubuf->build();
+
+ srb = rhi->newShaderResourceBindings()
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf)
+ });
+ srb->build();
+
+ ...
+
+ // now in a later frame we need to grow the buffer to a larger size
+ ubuf->setSize(512);
+ ubuf->build(); // same as ubuf->release(); ubuf->build();
+
+ // that's it, srb needs no changes whatsoever
+ \endcode
+
+ \section3 Pooled objects
+
+ In addition to resources, there are pooled objects as well, such as,
+ QRhiResourceUpdateBatch. An instance is retrieved via a \c next function,
+ such as, nextResourceUpdateBatch(). The caller does not own the returned
+ instance in this case. The only valid way of operating here is calling
+ functions on the QRhiResourceUpdateBatch and then passing it to
+ QRhiCommandBuffer::beginPass() or QRhiCommandBuffer::endPass(). These
+ functions take care of returning the batch to the pool. Alternatively, a
+ batch can be "canceled" and returned to the pool without processing by
+ calling QRhiResourceUpdateBatch::release().
+
+ A typical pattern is thus:
+
+ \badcode
+ QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch();
+ ...
+ resUpdates->updateDynamicBuffer(ubuf, 0, 64, mvp.constData());
+ if (!image.isNull()) {
+ resUpdates->uploadTexture(texture, image);
+ image = QImage();
+ }
+ ...
+ QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
+ cb->beginPass(swapchain->currentFrameRenderTarget(), clearCol, clearDs, resUpdates);
+ \endcode
+
+ \section3 Swapchain specifics
+
+ QRhiSwapChain features some special semantics due to the peculiar nature of
+ swapchains.
+
+ \list
+
+ \li It has no \c build but rather a QRhiSwapChain::buildOrResize().
+ Repeatedly calling this function is \b not the same as calling
+ QRhiSwapChain::release() followed by QRhiSwapChain::buildOrResize(). This
+ is because swapchains often have ways to handle the case where buffers need
+ to be resized in a manner that is more efficient than a brute force
+ destroying and recreating from scratch.
+
+ \li An active QRhiSwapChain must be released by calling
+ \l{QRhiSwapChain::release()}{release()}, or by destroying the object, before
+ the QWindow's underlying QPlatformWindow, and so the associated native
+ window object, is destroyed. It should not be postponed because releasing
+ the swapchain may become problematic (and with some APIs, like Vulkan, is
+ explicitly disallowed) when the native window is not around anymore, for
+ example because the QPlatformWindow got destroyed upon getting a
+ QWindow::close(). Therefore, releasing the swapchain must happen whenever
+ the targeted QWindow sends the
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed event. If the event does
+ not arrive before the destruction of the QWindow - this can happen when
+ using QCoreApplication::quit() -, then check QWindow::handle() after the
+ event loop exits and invoke the swapchain release when non-null (meaning
+ the underlying native window is still around).
+
+ \endlist
+
+ \section3 Ownership
+
+ The general rule is no ownership transfer. Creating a QRhi with an already
+ existing graphics device does not mean the QRhi takes ownership of the
+ device object. Similarly, ownership is not given away when a device or
+ texture object is "exported" via QRhi::nativeHandles() or
+ QRhiTexture::nativeHandles(). Most importantly, passing pointers in structs
+ and via setters does not transfer ownership.
+ */
+
+/*!
+ \enum QRhi::Implementation
+ Describes which graphics API-specific backend gets used by a QRhi instance.
+
+ \value Null
+ \value Vulkan
+ \value OpenGLES2
+ \value D3D11
+ \value Metal
+ */
+
+/*!
+ \enum QRhi::Flag
+ Describes what special features to enable.
+
+ \value EnableProfiling Enables gathering timing (CPU, GPU) and resource
+ (QRhiBuffer, QRhiTexture, etc.) information and additional metadata. See
+ QRhiProfiler. Avoid enabling in production builds as it may involve a
+ performance penalty.
+
+ \value EnableDebugMarkers Enables debug marker groups. Without this frame
+ debugging features like making debug groups and custom resource name
+ visible in external GPU debugging tools will not be available and functions
+ like QRhiCommandBuffer::debugMarkBegin() will become a no-op. Avoid
+ enabling in production builds as it may involve a performance penalty.
+ */
+
+/*!
+ \enum QRhi::FrameOpResult
+ Describes the result of operations that can have a soft failure.
+
+ \value FrameOpSuccess Success
+
+ \value FrameOpError Unspecified error
+
+ \value FrameOpSwapChainOutOfDate The swapchain is in an inconsistent state
+ internally. This can be recoverable by attempting to repeat the operation
+ (such as, beginFrame()) later.
+
+ \value FrameOpDeviceLost The graphics device was lost. This can be
+ recoverable by attempting to repeat the operation (such as, beginFrame())
+ and releasing and reinitializing all objects backed by native graphics
+ resources.
+ */
+
+/*!
+ \enum QRhi::Feature
+ Flag values to indicate what features are supported by the backend currently in use.
+
+ \value MultisampleTexture Indicates that textures with a sample count larger
+ than 1 are supported.
+
+ \value MultisampleRenderBuffer Indicates that renderbuffers with a sample
+ count larger than 1 are supported.
+
+ \value DebugMarkers Indicates that debug marker groups (and so
+ QRhiCommandBuffer::debugMarkBegin()) are supported.
+
+ \value Timestamps Indicates that command buffer timestamps are supported.
+ Relevant for QRhiProfiler::gpuFrameTimes().
+
+ \value Instancing Indicates that instanced drawing is supported.
+
+ \value CustomInstanceStepRate Indicates that instance step rates other than
+ 1 are supported.
+
+ \value PrimitiveRestart Indicates that restarting the assembly of
+ primitives when encountering an index value of 0xFFFF
+ (\l{QRhiCommandBuffer::IndexUInt16}{IndexUInt16}) or 0xFFFFFFFF
+ (\l{QRhiCommandBuffer::IndexUInt32}{IndexUInt32}) is enabled, for certain
+ primitive topologies at least. QRhi will try to enable this with all
+ backends, but in some cases it will not be supported. Dynamically
+ controlling primitive restart is not possible since with some APIs
+ primitive restart with a fixed index is always on. Applications must assume
+ that whenever this feature is reported as supported, the above mentioned
+ index values \c may be treated specially, depending on the topology. The
+ only two topologies where primitive restart is guaranteed to behave
+ identically across backends, as long as this feature is reported as
+ supported, are \l{QRhiGraphicsPipeline::LineStrip}{LineStrip} and
+ \l{QRhiGraphicsPipeline::TriangleStrip}{TriangleStrip}.
+
+ \value NonDynamicUniformBuffers Indicates that creating buffers with the
+ usage \l{QRhiBuffer::UniformBuffer}{UniformBuffer} and the types
+ \l{QRhiBuffer::Immutable}{Immutable} or \l{QRhiBuffer::Static}{Static} is
+ supported. When reported as unsupported, uniform (constant) buffers must be
+ created as \l{QRhiBuffer::Dynamic}{Dynamic}. (which is recommended
+ regardless)
+
+ \value NonFourAlignedEffectiveIndexBufferOffset Indicates that effective
+ index buffer offsets (\c{indexOffset + firstIndex * indexComponentSize})
+ that are not 4 byte aligned are supported. When not supported, attempting
+ to issue a \l{QRhiCommandBuffer::drawIndexed()}{drawIndexed()} with a
+ non-aligned effective offset may lead to unspecified behavior.
+
+ \value NPOTTextureRepeat Indicates that the \l{QRhiSampler::Repeat}{Repeat}
+ mode is supported for textures with a non-power-of-two size.
+
+ \value RedOrAlpha8IsRed Indicates that the
+ \l{QRhiTexture::RED_OR_ALPHA8}{RED_OR_ALPHA8} format maps to a one
+ component 8-bit \c red format. This is the case for all backends except
+ OpenGL, where \c{GL_ALPHA}, a one component 8-bit \c alpha format, is used
+ instead. This is relevant for shader code that samples from the texture.
+
+ \value ElementIndexUint Indicates that 32-bit unsigned integer elements are
+ supported in the index buffer. In practice this is true everywhere except
+ when running on plain OpenGL ES 2.0 implementations without the necessary
+ extension. When false, only 16-bit unsigned elements are supported in the
+ index buffer.
+
+ \value Compute Indicates that compute shaders, image load/store, and
+ storage buffers are supported.
+
+ \value WideLines Indicates that lines with a width other than 1 are
+ supported. When reported as not supported, the line width set on the
+ graphics pipeline state is ignored. This can always be false with some
+ backends (D3D11, Metal). With Vulkan, the value depends on the
+ implementation.
+
+ \value VertexShaderPointSize Indicates that the size of rasterized points
+ set via \c{gl_PointSize} in the vertex shader is taken into account. When
+ reported as not supported, drawing points with a size other than 1 is not
+ supported. Setting \c{gl_PointSize} in the shader is still valid then, but
+ is ignored. (for example, when generating HLSL, the assignment is silently
+ dropped from the generated code) Note that some APIs (Metal, Vulkan)
+ require the point size to be set in the shader explicitly whenever drawing
+ points, even when the size is 1, as they do not automatically default to 1.
+
+ \value BaseVertex Indicates that \l{QRhiCommandBuffer::drawIndexed()}{drawIndexed()}
+ supports the \c vertexOffset argument. When reported as not supported, the
+ vertexOffset value in an indexed draw is ignored.
+
+ \value BaseInstance Indicates that instanced draw commands support the \c
+ firstInstance argument. When reported as not supported, the firstInstance
+ value is ignored and the instance ID starts from 0.
+ */
+
+/*!
+ \enum QRhi::BeginFrameFlag
+ Flag values for QRhi::beginFrame()
+ */
+
+/*!
+ \enum QRhi::EndFrameFlag
+ Flag values for QRhi::endFrame()
+
+ \value SkipPresent Specifies that no present command is to be queued or no
+ swapBuffers call is to be made. This way no image is presented. Generating
+ multiple frames with all having this flag set is not recommended (except,
+ for example, for benchmarking purposes - but keep in mind that backends may
+ behave differently when it comes to waiting for command completion without
+ presenting so the results are not comparable between them)
+ */
+
+/*!
+ \enum QRhi::ResourceLimit
+ Describes the resource limit to query.
+
+ \value TextureSizeMin Minimum texture width and height. This is typically
+ 1. The minimum texture size is handled gracefully, meaning attempting to
+ create a texture with an empty size will instead create a texture with the
+ minimum size.
+
+ \value TextureSizeMax Maximum texture width and height. This depends on the
+ graphics API and sometimes the platform or implementation as well.
+ Typically the value is in the range 4096 - 16384. Attempting to create
+ textures larger than this is expected to fail.
+
+ \value MaxColorAttachments The maximum number of color attachments for a
+ QRhiTextureRenderTarget, in case multiple render targets are supported. When
+ MRT is not supported, the value is 1. Otherwise this is typically 8, but
+ watch out for the fact that OpenGL only mandates 4 as the minimum, and that
+ is what some OpenGL ES implementations provide.
+
+ \value FramesInFlight The number of frames the backend may keep "in
+ flight". The value has no relevance, and is unspecified, with backends like
+ OpenGL and Direct3D 11. With backends like Vulkan or Metal, it is the
+ responsibility of QRhi to block whenever starting a new frame and finding
+ the CPU is already \c{N - 1} frames ahead of the GPU (because the command
+ buffer submitted in frame no. \c{current} - \c{N} has not yet completed).
+ The value N is what is returned from here, and is typically 2. This can be
+ relevant to applications that integrate rendering done directly with the
+ graphics API, as such rendering code may want to perform double (if the
+ value is 2) buffering for resources, such as, buffers, similarly to the
+ QRhi backends themselves. The current frame slot index (a value running 0,
+ 1, .., N-1, then wrapping around) is retrievable from
+ QRhi::currentFrameSlot().
+ */
+
+/*!
+ \class QRhiInitParams
+ \inmodule QtRhi
+ \brief Base class for backend-specific initialization parameters.
+
+ Contains fields that are relevant to all backends.
+ */
+
+/*!
+ \class QRhiDepthStencilClearValue
+ \inmodule QtRhi
+ \brief Specifies clear values for a depth or stencil buffer.
+ */
+
+/*!
+ \fn QRhiDepthStencilClearValue::QRhiDepthStencilClearValue()
+
+ Constructs a depth/stencil clear value with depth clear value 1.0f and
+ stencil clear value 0.
+ */
+
+/*!
+ Constructs a depth/stencil clear value with depth clear value \a d and
+ stencil clear value \a s.
+ */
+QRhiDepthStencilClearValue::QRhiDepthStencilClearValue(float d, quint32 s)
+ : m_d(d),
+ m_s(s)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiDepthStencilClearValue objects
+ \a a and \a b are equal.
+
+ \relates QRhiDepthStencilClearValue
+ */
+bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
+{
+ return a.depthClearValue() == b.depthClearValue()
+ && a.stencilClearValue() == b.stencilClearValue();
+}
+
+/*!
+ \return \c false if the values in the two QRhiDepthStencilClearValue
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiDepthStencilClearValue
+*/
+bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiDepthStencilClearValue
+ */
+uint qHash(const QRhiDepthStencilClearValue &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed * (qFloor(v.depthClearValue() * 100) + v.stencilClearValue());
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiDepthStencilClearValue(depth-clear=" << v.depthClearValue()
+ << " stencil-clear=" << v.stencilClearValue()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiViewport
+ \inmodule QtRhi
+ \brief Specifies a viewport rectangle.
+
+ Used with QRhiCommandBuffer::setViewport().
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ Typical usage is like the following:
+
+ \badcode
+ const QSize outputSizeInPixels = swapchain->currentPixelSize();
+ const QRhiViewport viewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height());
+ cb->beginPass(swapchain->currentFrameRenderTarget(), { 0, 0, 0, 1 }, { 1, 0 });
+ cb->setGraphicsPipeline(ps);
+ cb->setViewport(viewport);
+ ...
+ \endcode
+
+ \sa QRhiCommandBuffer::setViewport(), QRhi::clipSpaceCorrMatrix(), QRhiScissor
+ */
+
+/*!
+ \fn QRhiViewport::QRhiViewport()
+
+ Constructs a viewport description with an empty rectangle and a depth range
+ of 0.0f - 1.0f.
+
+ \sa QRhi::clipSpaceCorrMatrix()
+ */
+
+/*!
+ Constructs a viewport description with the rectangle specified by \a x, \a
+ y, \a w, \a h and the depth range \a minDepth and \a maxDepth.
+
+ \note x and y are assumed to be the bottom-left position.
+
+ \sa QRhi::clipSpaceCorrMatrix()
+ */
+QRhiViewport::QRhiViewport(float x, float y, float w, float h, float minDepth, float maxDepth)
+ : m_rect { { x, y, w, h } },
+ m_minDepth(minDepth),
+ m_maxDepth(maxDepth)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiViewport objects
+ \a a and \a b are equal.
+
+ \relates QRhiViewport
+ */
+bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
+{
+ return a.viewport() == b.viewport()
+ && a.minDepth() == b.minDepth()
+ && a.maxDepth() == b.maxDepth();
+}
+
+/*!
+ \return \c false if the values in the two QRhiViewport
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiViewport
+*/
+bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiViewport
+ */
+uint qHash(const QRhiViewport &v, uint seed) Q_DECL_NOTHROW
+{
+ const std::array<float, 4> r = v.viewport();
+ return seed + r[0] + r[1] + r[2] + r[3] + qFloor(v.minDepth() * 100) + qFloor(v.maxDepth() * 100);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiViewport &v)
+{
+ QDebugStateSaver saver(dbg);
+ const std::array<float, 4> r = v.viewport();
+ dbg.nospace() << "QRhiViewport(bottom-left-x=" << r[0]
+ << " bottom-left-y=" << r[1]
+ << " width=" << r[2]
+ << " height=" << r[3]
+ << " minDepth=" << v.minDepth()
+ << " maxDepth=" << v.maxDepth()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiScissor
+ \inmodule QtRhi
+ \brief Specifies a scissor rectangle.
+
+ Used with QRhiCommandBuffer::setScissor(). Setting a scissor rectangle is
+ only possible with a QRhiGraphicsPipeline that has
+ QRhiGraphicsPipeline::UsesScissor set.
+
+ \note QRhi assumes OpenGL-style scissor coordinates, meaning x and y are
+ bottom-left.
+
+ \sa QRhiCommandBuffer::setScissor(), QRhiViewport
+ */
+
+/*!
+ \fn QRhiScissor::QRhiScissor()
+
+ Constructs an empty scissor.
+ */
+
+/*!
+ Constructs a scissor with the rectangle specified by \a x, \a y, \a w, and
+ \a h.
+
+ \note x and y are assumed to be the bottom-left position.
+ */
+QRhiScissor::QRhiScissor(int x, int y, int w, int h)
+ : m_rect { { x, y, w, h } }
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiScissor objects
+ \a a and \a b are equal.
+
+ \relates QRhiScissor
+ */
+bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
+{
+ return a.scissor() == b.scissor();
+}
+
+/*!
+ \return \c false if the values in the two QRhiScissor
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiScissor
+*/
+bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiScissor
+ */
+uint qHash(const QRhiScissor &v, uint seed) Q_DECL_NOTHROW
+{
+ const std::array<int, 4> r = v.scissor();
+ return seed + r[0] + r[1] + r[2] + r[3];
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiScissor &s)
+{
+ QDebugStateSaver saver(dbg);
+ const std::array<int, 4> r = s.scissor();
+ dbg.nospace() << "QRhiScissor(bottom-left-x=" << r[0]
+ << " bottom-left-y=" << r[1]
+ << " width=" << r[2]
+ << " height=" << r[3]
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputBinding
+ \inmodule QtRhi
+ \brief Describes a vertex input binding.
+
+ Specifies the stride (in bytes, must be a multiple of 4), the
+ classification and optionally the instance step rate.
+
+ As an example, assume a vertex shader with the following inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec2 texcoord;
+ \endcode
+
+ Now let's assume also that 3 component vertex positions \c{(x, y, z)} and 2
+ component texture coordinates \c{(u, v)} are provided in a non-interleaved
+ format in a buffer (or separate buffers even). Definining two bindings
+ could then be done like this:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) },
+ { 2 * sizeof(float) }
+ });
+ \endcode
+
+ Only the stride is interesting here since instancing is not used. The
+ binding number is given by the index of the QRhiVertexInputBinding
+ element in the bindings vector of the QRhiVertexInputLayout.
+
+ Once a graphics pipeline with this vertex input layout is bound, the vertex
+ inputs could be set up like the following for drawing a cube with 36
+ vertices, assuming we have a single buffer with first the positions and
+ then the texture coordinates:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { cubeBuf, 0 },
+ { cubeBuf, 36 * 3 * sizeof(float) }
+ };
+ cb->setVertexInput(0, 2, vbufBindings);
+ \endcode
+
+ Note how the index defined by \c {startBinding + i}, where \c i is the
+ index in the second argument of
+ \l{QRhiCommandBuffer::setVertexInput()}{setVertexInput()}, matches the
+ index of the corresponding entry in the \c bindings vector of the
+ QRhiVertexInputLayout.
+
+ \note the stride must always be a multiple of 4.
+
+ \sa QRhiCommandBuffer::setVertexInput()
+ */
+
+/*!
+ \enum QRhiVertexInputBinding::Classification
+ Describes the input data classification.
+
+ \value PerVertex Data is per-vertex
+ \value PerInstance Data is per-instance
+ */
+
+/*!
+ \fn QRhiVertexInputBinding::QRhiVertexInputBinding()
+
+ Constructs a default vertex input binding description.
+ */
+
+/*!
+ Constructs a vertex input binding description with the specified \a stride,
+ classification \a cls, and instance step rate \a stepRate.
+
+ \note \a stepRate other than 1 is only supported when
+ QRhi::CustomInstanceStepRate is reported to be supported.
+ */
+QRhiVertexInputBinding::QRhiVertexInputBinding(quint32 stride, Classification cls, int stepRate)
+ : m_stride(stride),
+ m_classification(cls),
+ m_instanceStepRate(stepRate)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputBinding objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputBinding
+ */
+bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
+{
+ return a.stride() == b.stride()
+ && a.classification() == b.classification()
+ && a.instanceStepRate() == b.instanceStepRate();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputBinding
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputBinding
+*/
+bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputBinding
+ */
+uint qHash(const QRhiVertexInputBinding &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed + v.stride() + v.classification();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputBinding(stride=" << b.stride()
+ << " cls=" << b.classification()
+ << " step-rate=" << b.instanceStepRate()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputAttribute
+ \inmodule QtRhi
+ \brief Describes a single vertex input element.
+
+ The members specify the binding number, location, format, and offset for a
+ single vertex input element.
+
+ \note For HLSL it is assumed that the vertex shader uses
+ \c{TEXCOORD<location>} as the semantic for each input. Hence no separate
+ semantic name and index.
+
+ As an example, assume a vertex shader with the following inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec2 texcoord;
+ \endcode
+
+ Now let's assume that we have 3 component vertex positions \c{(x, y, z)}
+ and 2 component texture coordinates \c{(u, v)} are provided in a
+ non-interleaved format in a buffer (or separate buffers even). Once two
+ bindings are defined, the attributes could be specified as:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 3 * sizeof(float) },
+ { 2 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 1, 1, QRhiVertexInputAttribute::Float2, 0 }
+ });
+ \endcode
+
+ Once a graphics pipeline with this vertex input layout is bound, the vertex
+ inputs could be set up like the following for drawing a cube with 36
+ vertices, assuming we have a single buffer with first the positions and
+ then the texture coordinates:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBindings[] = {
+ { cubeBuf, 0 },
+ { cubeBuf, 36 * 3 * sizeof(float) }
+ };
+ cb->setVertexInput(0, 2, vbufBindings);
+ \endcode
+
+ When working with interleaved data, there will typically be just one
+ binding, with multiple attributes referring to that same buffer binding
+ point:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float3, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float2, 3 * sizeof(float) }
+ });
+ \endcode
+
+ and then:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBinding(interleavedCubeBuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ \endcode
+
+ \sa QRhiCommandBuffer::setVertexInput()
+ */
+
+/*!
+ \enum QRhiVertexInputAttribute::Format
+ Specifies the type of the element data.
+
+ \value Float4 Four component float vector
+ \value Float3 Three component float vector
+ \value Float2 Two component float vector
+ \value Float Float
+ \value UNormByte4 Four component normalized unsigned byte vector
+ \value UNormByte2 Two component normalized unsigned byte vector
+ \value UNormByte Normalized unsigned byte
+ */
+
+/*!
+ \fn QRhiVertexInputAttribute::QRhiVertexInputAttribute()
+
+ Constructs a default vertex input attribute description.
+ */
+
+/*!
+ Constructs a vertex input attribute description with the specified \a
+ binding number, \a location, \a format, and \a offset.
+ */
+QRhiVertexInputAttribute::QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset)
+ : m_binding(binding),
+ m_location(location),
+ m_format(format),
+ m_offset(offset)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputAttribute objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputAttribute
+ */
+bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
+{
+ return a.binding() == b.binding()
+ && a.location() == b.location()
+ && a.format() == b.format()
+ && a.offset() == b.offset();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputAttribute
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputAttribute
+*/
+bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputAttribute
+ */
+uint qHash(const QRhiVertexInputAttribute &v, uint seed) Q_DECL_NOTHROW
+{
+ return seed + v.binding() + v.location() + v.format() + v.offset();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputAttribute &a)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputAttribute(binding=" << a.binding()
+ << " location=" << a.location()
+ << " format=" << a.format()
+ << " offset=" << a.offset()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiVertexInputLayout
+ \inmodule QtRhi
+ \brief Describes the layout of vertex inputs consumed by a vertex shader.
+
+ The vertex input layout is defined by the collections of
+ QRhiVertexInputBinding and QRhiVertexInputAttribute.
+ */
+
+/*!
+ \fn QRhiVertexInputLayout::QRhiVertexInputLayout()
+
+ Constructs an empty vertex input layout description.
+ */
+
+/*!
+ \return \c true if the values in the two QRhiVertexInputLayout objects
+ \a a and \a b are equal.
+
+ \relates QRhiVertexInputLayout
+ */
+bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
+{
+ return a.bindings() == b.bindings()
+ && a.attributes() == b.attributes();
+}
+
+/*!
+ \return \c false if the values in the two QRhiVertexInputLayout
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiVertexInputLayout
+*/
+bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiVertexInputLayout
+ */
+uint qHash(const QRhiVertexInputLayout &v, uint seed) Q_DECL_NOTHROW
+{
+ return qHash(v.bindings(), seed) + qHash(v.attributes(), seed);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiVertexInputLayout(bindings=" << v.bindings()
+ << " attributes=" << v.attributes()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiShaderStage
+ \inmodule QtRhi
+ \brief Specifies the type and the shader code for a shader stage in the pipeline.
+ */
+
+/*!
+ \enum QRhiShaderStage::Type
+ Specifies the type of the shader stage.
+
+ \value Vertex Vertex stage
+ \value Fragment Fragment (pixel) stage
+ \value Compute Compute stage (this may not always be supported at run time)
+ */
+
+/*!
+ \fn QRhiShaderStage::QRhiShaderStage()
+
+ Constructs a shader stage description for the vertex stage with an empty
+ QShader.
+ */
+
+/*!
+ Constructs a shader stage description with the \a type of the stage and the
+ \a shader.
+
+ The shader variant \a v defaults to QShader::StandardShader. A
+ QShader contains multiple source and binary versions of a shader.
+ In addition, it can also contain variants of the shader with slightly
+ modified code. \a v can then be used to select the desired variant.
+ */
+QRhiShaderStage::QRhiShaderStage(Type type, const QShader &shader, QShader::Variant v)
+ : m_type(type),
+ m_shader(shader),
+ m_shaderVariant(v)
+{
+}
+
+/*!
+ \return \c true if the values in the two QRhiShaderStage objects
+ \a a and \a b are equal.
+
+ \relates QRhiShaderStage
+ */
+bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW
+{
+ return a.type() == b.type()
+ && a.shader() == b.shader()
+ && a.shaderVariant() == b.shaderVariant();
+}
+
+/*!
+ \return \c false if the values in the two QRhiShaderStage
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiShaderStage
+*/
+bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a v, using \a seed to seed the calculation.
+
+ \relates QRhiShaderStage
+ */
+uint qHash(const QRhiShaderStage &v, uint seed) Q_DECL_NOTHROW
+{
+ return v.type() + qHash(v.shader(), seed) + v.shaderVariant();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiShaderStage &s)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiShaderStage(type=" << s.type()
+ << " shader=" << s.shader()
+ << " variant=" << s.shaderVariant()
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiColorAttachment
+ \inmodule QtRhi
+ \brief Describes the a single color attachment of a render target.
+
+ A color attachment is either a QRhiTexture or a QRhiRenderBuffer. The
+ former, when texture() is set, is used in most cases.
+
+ \note texture() and renderBuffer() cannot be both set (be non-null at the
+ same time).
+
+ Setting renderBuffer instead is recommended only when multisampling is
+ needed. Relying on QRhi::MultisampleRenderBuffer is a better choice than
+ QRhi::MultisampleTexture in practice since the former is available in more
+ run time configurations (e.g. when running on OpenGL ES 3.0 which has no
+ support for multisample textures, but does support multisample
+ renderbuffers).
+
+ When targeting a non-multisample texture, the layer() and level()
+ indicate the targeted layer (face index \c{0-5} for cubemaps) and mip
+ level.
+
+ When texture() or renderBuffer() is multisample, resolveTexture() can be
+ set optionally. When set, samples are resolved automatically into that
+ (non-multisample) texture at the end of the render pass. When rendering
+ into a multisample renderbuffers, this is the only way to get resolved,
+ non-multisample content out of them. Multisample textures allow sampling in
+ shaders so for them this is just one option.
+
+ \note when resolving is enabled, the multisample data may not be written
+ out at all. This means that the multisample texture() must not be used
+ afterwards with shaders for sampling when resolveTexture() is set.
+ */
+
+/*!
+ \fn QRhiColorAttachment::QRhiColorAttachment()
+
+ Constructs an empty color attachment description.
+ */
+
+/*!
+ Constructs a color attachment description that specifies \a texture as the
+ associated color buffer.
+ */
+QRhiColorAttachment::QRhiColorAttachment(QRhiTexture *texture)
+ : m_texture(texture)
+{
+}
+
+/*!
+ Constructs a color attachment description that specifies \a renderBuffer as
+ the associated color buffer.
+ */
+QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
+ : m_renderBuffer(renderBuffer)
+{
+}
+
+/*!
+ \class QRhiTextureRenderTargetDescription
+ \inmodule QtRhi
+ \brief Describes the color and depth or depth/stencil attachments of a render target.
+
+ A texture render target has zero or more textures as color attachments,
+ zero or one renderbuffer as combined depth/stencil buffer or zero or one
+ texture as depth buffer.
+
+ \note depthStencilBuffer() and depthTexture() cannot be both set (cannot be
+ non-null at the same time).
+ */
+
+/*!
+ \fn QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription()
+
+ Constructs an empty texture render target description.
+ */
+
+/*!
+ Constructs a texture render target description with one attachment
+ described by \a colorAttachment.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ Constructs a texture render target description with two attachments, a
+ color attachment described by \a colorAttachment, and a depth/stencil
+ attachment with \a depthStencilBuffer.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment,
+ QRhiRenderBuffer *depthStencilBuffer)
+ : m_depthStencilBuffer(depthStencilBuffer)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ Constructs a texture render target description with two attachments, a
+ color attachment described by \a colorAttachment, and a depth attachment
+ with \a depthTexture.
+
+ \note \a depthTexture must have a suitable format, such as QRhiTexture::D16
+ or QRhiTexture::D32F.
+ */
+QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment,
+ QRhiTexture *depthTexture)
+ : m_depthTexture(depthTexture)
+{
+ m_colorAttachments.append(colorAttachment);
+}
+
+/*!
+ \class QRhiTextureSubresourceUploadDescription
+ \inmodule QtRhi
+ \brief Describes the source for one mip level in a layer in a texture upload operation.
+
+ The source content is specified either as a QImage or as a raw blob. The
+ former is only allowed for uncompressed textures with a format that can be
+ mapped to QImage, while the latter is supported for all formats, including
+ floating point and compressed.
+
+ \note image() and data() cannot be both set at the same time.
+
+ destinationTopLeft() specifies the top-left corner of the target
+ rectangle. Defaults to (0, 0).
+
+ An empty sourceSize() (the default) indicates that size is assumed to be
+ the size of the subresource. With QImage-based uploads this implies that
+ the size of the source image() must match the subresource. When providing
+ raw data instead, sufficient number of bytes must be provided in data().
+
+ \note With compressed textures the first upload must always match the
+ subresource size due to graphics API limitations with some backends.
+
+ sourceTopLeft() is supported only for QImage-based uploads, and specifies
+ the top-left corner of the source rectangle.
+
+ \note Setting sourceSize() or sourceTopLeft() may trigger a QImage copy
+ internally, depending on the format and the backend.
+
+ When providing raw data, the stride (row pitch, row length in bytes) of the
+ provided data must be equal to \c{width * pixelSize} where \c pixelSize is
+ the number of bytes used for one pixel, and there must be no additional
+ padding between rows. There is no row start alignment requirement.
+
+ \note The format of the source data must be compatible with the texture
+ format. With many graphics APIs the data is copied as-is into a staging
+ buffer, there is no intermediate format conversion provided by QRhi. This
+ applies to floating point formats as well, with, for example, RGBA16F
+ requiring half floats in the source data.
+ */
+
+/*!
+ \fn QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription()
+
+ Constructs an empty subresource description.
+
+ \note an empty QRhiTextureSubresourceUploadDescription is not useful on its
+ own and should not be submitted to a QRhiTextureUploadEntry. At minimum
+ image or data must be set first.
+ */
+
+/*!
+ Constructs a mip level description with a \a image.
+
+ The \l{QImage::size()}{size} of \a image must match the size of the mip
+ level. For level 0 that is the \l{QRhiTexture::pixelSize()}{texture size}.
+
+ The bit depth of \a image must be compatible with the
+ \l{QRhiTexture::Format}{texture format}.
+
+ To describe a partial upload, call setSourceSize(), setSourceTopLeft(), or
+ setDestinationTopLeft() afterwards.
+ */
+QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(const QImage &image)
+ : m_image(image)
+{
+}
+
+/*!
+ Constructs a mip level description with the image data is specified by \a
+ data and \a size. This is suitable for floating point and compressed
+ formats as well.
+
+ \a data can safely be destroyed or changed once this function returns.
+ */
+QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription(const void *data, int size)
+ : m_data(reinterpret_cast<const char *>(data), size)
+{
+}
+
+/*!
+ \class QRhiTextureUploadEntry
+ \inmodule QtRhi
+ \brief Describes one layer (face for cubemaps) in a texture upload operation.
+ */
+
+/*!
+ \fn QRhiTextureUploadEntry::QRhiTextureUploadEntry()
+
+ Constructs an empty QRhiTextureUploadEntry targeting layer 0 and level 0.
+
+ \note an empty QRhiTextureUploadEntry should not be submitted without
+ setting a QRhiTextureSubresourceUploadDescription via setDescription()
+ first.
+ */
+
+/*!
+ Constructs a QRhiTextureUploadEntry targeting the given \a layer and mip
+ \a level, with the subresource contents described by \a desc.
+ */
+QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &desc)
+ : m_layer(layer),
+ m_level(level),
+ m_desc(desc)
+{
+}
+
+/*!
+ \class QRhiTextureUploadDescription
+ \inmodule QtRhi
+ \brief Describes a texture upload operation.
+
+ Used with QRhiResourceUpdateBatch::uploadTexture(). That function has two
+ variants: one taking a QImage and one taking a
+ QRhiTextureUploadDescription. The former is a convenience version,
+ internally creating a QRhiTextureUploadDescription with a single image
+ targeting level 0 for layer 0. However, when cubemaps, pre-generated mip
+ images, or compressed textures are involved, applications will have to work
+ directly with this class instead.
+
+ QRhiTextureUploadDescription also enables specifying batched uploads, which
+ are useful for example when generating an atlas or glyph cache texture:
+ multiple, partial uploads for the same subresource (meaning the same layer
+ and level) are supported, and can be, depending on the backend and the
+ underlying graphics API, more efficient when batched into the same
+ QRhiTextureUploadDescription as opposed to issuing individual
+ \l{QRhiResourceUpdateBatch::uploadTexture()}{uploadTexture()} commands for
+ each of them.
+
+ \note Cubemaps have one layer for each of the six faces in the order +X,
+ -X, +Y, -Y, +Z, -Z.
+
+ For example, specifying the faces of a cubemap could look like the following:
+
+ \badcode
+ QImage faces[6];
+ ...
+ QVector<QRhiTextureUploadEntry> entries;
+ for (int i = 0; i < 6; ++i)
+ entries.append(QRhiTextureUploadEntry(i, 0, faces[i]));
+ QRhiTextureUploadDescription desc(entries);
+ resourceUpdates->uploadTexture(texture, desc);
+ \endcode
+
+ Another example that specifies mip images for a compressed texture:
+
+ \badcode
+ QRhiTextureUploadDescription desc;
+ const int mipCount = rhi->mipLevelsForSize(compressedTexture->pixelSize());
+ for (int level = 0; level < mipCount; ++level) {
+ const QByteArray compressedDataForLevel = ..
+ desc.append(QRhiTextureUploadEntry(0, level, compressedDataForLevel));
+ }
+ resourceUpdates->uploadTexture(compressedTexture, desc);
+ \endcode
+
+ With partial uploads targeting the same subresource, it is recommended to
+ batch them into a single upload request, whenever possible:
+
+ \badcode
+ QRhiTextureSubresourceUploadDescription subresDesc(image);
+ subresDesc.setSourceSize(QSize(10, 10));
+ subResDesc.setDestinationTopLeft(QPoint(50, 40));
+ QRhiTextureUploadEntry entry(0, 0, subresDesc); // layer 0, level 0
+
+ QRhiTextureSubresourceUploadDescription subresDesc2(image);
+ subresDesc2.setSourceSize(QSize(30, 40));
+ subResDesc2.setDestinationTopLeft(QPoint(100, 200));
+ QRhiTextureUploadEntry entry2(0, 0, subresDesc2); // layer 0, level 0, i.e. same subresource
+
+ QRhiTextureUploadDescription desc({ entry, entry2});
+ resourceUpdates->uploadTexture(texture, desc);
+ \endcode
+ */
+
+/*!
+ \fn QRhiTextureUploadDescription::QRhiTextureUploadDescription()
+
+ Constructs an empty texture upload description.
+ */
+
+/*!
+ Constructs a texture upload description with a single subresource upload
+ described by \a entry.
+ */
+QRhiTextureUploadDescription::QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry)
+{
+ m_entries.append(entry);
+}
+
+/*!
+ Constructs a texture upload description with the specified list of \a entries.
+
+ \note \a entries can also contain multiple QRhiTextureUploadEntry elements
+ with the the same layer and level. This makes sense when those uploads are
+ partial, meaning their subresource description has a source size or image
+ smaller than the subresource dimensions, and can be more efficient than
+ issuing separate uploadTexture()'s.
+ */
+QRhiTextureUploadDescription::QRhiTextureUploadDescription(const QVector<QRhiTextureUploadEntry> &entries)
+ : m_entries(entries)
+{
+}
+
+/*!
+ Adds \a entry to the list of subresource uploads.
+ */
+void QRhiTextureUploadDescription::append(const QRhiTextureUploadEntry &entry)
+{
+ m_entries.append(entry);
+}
+
+/*!
+ \class QRhiTextureCopyDescription
+ \inmodule QtRhi
+ \brief Describes a texture-to-texture copy operation.
+
+ An empty pixelSize() indicates that the entire subresource is to be copied.
+ A default constructed copy description therefore leads to copying the
+ entire subresource at level 0 of layer 0.
+
+ \note The source texture must be created with
+ QRhiTexture::UsedAsTransferSource.
+
+ \note The source and destination rectangles defined by pixelSize(),
+ sourceTopLeft(), and destinationTopLeft() must fit the source and
+ destination textures, respectively. The behavior is undefined otherwise.
+ */
+
+/*!
+ \fn QRhiTextureCopyDescription::QRhiTextureCopyDescription()
+
+ Constructs an empty texture copy description.
+ */
+
+/*!
+ \class QRhiReadbackDescription
+ \inmodule QtRhi
+ \brief Describes a readback (reading back texture contents from possibly GPU-only memory) operation.
+
+ The source of the readback operation is either a QRhiTexture or the
+ current backbuffer of the currently targeted QRhiSwapChain. When
+ texture() is not set, the swapchain is used. Otherwise the specified
+ QRhiTexture is treated as the source.
+
+ \note Textures used in readbacks must be created with
+ QRhiTexture::UsedAsTransferSource.
+
+ \note Swapchains used in readbacks must be created with
+ QRhiSwapChain::UsedAsTransferSource.
+
+ layer() and level() are only applicable when the source is a QRhiTexture.
+
+ \note Multisample textures cannot be read back. Readbacks are supported for
+ multisample swapchain buffers however.
+ */
+
+/*!
+ \fn QRhiReadbackDescription::QRhiReadbackDescription()
+
+ Constructs an empty texture readback description.
+
+ \note The source texture is set to null by default, which is still a valid
+ readback: it specifies that the backbuffer of the current swapchain is to
+ be read back. (current meaning the frame's target swapchain at the time of
+ committing the QRhiResourceUpdateBatch with the
+ \l{QRhiResourceUpdateBatch::readBackTexture()}{texture readback} on it)
+ */
+
+/*!
+ Constructs an texture readback description that specifies that level 0 of
+ layer 0 of \a texture is to be read back.
+
+ \note \a texture can also be null in which case this constructor is
+ identical to the argumentless variant.
+ */
+QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
+ : m_texture(texture)
+{
+}
+
+/*!
+ \class QRhiReadbackResult
+ \inmodule QtRhi
+ \brief Describes the results of a potentially asynchronous readback operation.
+
+ When \l completed is set, the function is invoked when the \l data is
+ available. \l format and \l pixelSize are set upon completion together with
+ \l data.
+ */
+
+/*!
+ \class QRhiNativeHandles
+ \inmodule QtRhi
+ \brief Base class for classes exposing backend-specific collections of native resource objects.
+ */
+
+/*!
+ \class QRhiResource
+ \inmodule QtRhi
+ \brief Base class for classes encapsulating native resource objects.
+ */
+
+/*!
+ \fn QRhiResource::Type QRhiResource::resourceType() const
+
+ \return the type of the resource.
+ */
+
+/*!
+ \internal
+ */
+QRhiResource::QRhiResource(QRhiImplementation *rhi)
+ : m_rhi(rhi)
+{
+ m_id = QRhiGlobalObjectIdGenerator::newId();
+}
+
+/*!
+ Destructor.
+
+ Releases (or requests deferred releasing of) the underlying native graphics
+ resources, if there are any.
+
+ \note Resources referenced by commands for the current frame should not be
+ released until the frame is submitted by QRhi::endFrame().
+
+ \sa release()
+ */
+QRhiResource::~QRhiResource()
+{
+ // release() cannot be called here, it being virtual; it is up to the
+ // subclasses to do that.
+}
+
+/*!
+ \fn void QRhiResource::release()
+
+ Releases (or requests deferred releasing of) the underlying native graphics
+ resources. Safe to call multiple times, subsequent invocations will be a
+ no-op then.
+
+ Once release() is called, the QRhiResource instance can be reused, by
+ calling \c build() again. That will then result in creating new native
+ graphics resources underneath.
+
+ \note Resources referenced by commands for the current frame should not be
+ released until the frame is submitted by QRhi::endFrame().
+
+ The QRhiResource destructor also performs the same task, so calling this
+ function is not necessary before destroying a QRhiResource.
+
+ \sa releaseAndDestroyLater()
+ */
+
+/*!
+ When called without a frame being recorded, this function is equivalent to
+ deleting the object. Between a QRhi::beginFrame() and QRhi::endFrame()
+ however the behavior is different: the QRhiResource will not be destroyed
+ until the frame is submitted via QRhi::endFrame(), thus satisfying the QRhi
+ requirement of not altering QRhiResource objects that are referenced by the
+ frame being recorded.
+
+ \sa release()
+ */
+void QRhiResource::releaseAndDestroyLater()
+{
+ m_rhi->addReleaseAndDestroyLater(this);
+}
+
+/*!
+ \return the currently set object name. By default the name is empty.
+ */
+QByteArray QRhiResource::name() const
+{
+ return m_objectName;
+}
+
+/*!
+ Sets a \a name for the object.
+
+ This has two uses: to get descriptive names for the native graphics
+ resources visible in graphics debugging tools, such as
+ \l{https://renderdoc.org/}{RenderDoc} and
+ \l{https://developer.apple.com/xcode/}{XCode}, and in the output stream of
+ QRhiProfiler.
+
+ When it comes to naming native objects by relaying the name via the
+ appropriate graphics API, note that the name is ignored when
+ QRhi::DebugMarkers are not supported, and may, depending on the backend,
+ also be ignored when QRhi::EnableDebugMarkers is not set.
+
+ \note The name may be ignored for objects other than buffers,
+ renderbuffers, and textures, depending on the backend.
+
+ \note The name may be modified. For slotted resources, such as a QRhiBuffer
+ backed by multiple native buffers, QRhi will append a suffix to make the
+ underlying native buffers easily distinguishable from each other.
+ */
+void QRhiResource::setName(const QByteArray &name)
+{
+ m_objectName = name;
+ m_objectName.replace(',', '_'); // cannot contain comma for QRhiProfiler
+}
+
+/*!
+ \return the global, unique identifier of this QRhiResource.
+
+ User code rarely needs to deal with the value directly. It is used
+ internally for tracking and bookkeeping purposes.
+ */
+quint64 QRhiResource::globalResourceId() const
+{
+ return m_id;
+}
+
+/*!
+ \class QRhiBuffer
+ \inmodule QtRhi
+ \brief Vertex, index, or uniform (constant) buffer resource.
+ */
+
+/*!
+ \enum QRhiBuffer::Type
+ Specifies storage type of buffer resource.
+
+ \value Immutable Indicates that the data is not expected to change ever
+ after the initial upload. Under the hood such buffer resources are
+ typically placed in device local (GPU) memory (on systems where
+ applicable). Uploading new data is possible, but may be expensive. The
+ upload typically happens by copying to a separate, host visible staging
+ buffer from which a GPU buffer-to-buffer copy is issued into the actual
+ GPU-only buffer.
+
+ \value Static Indicates that the data is expected to change only
+ infrequently. Typically placed in device local (GPU) memory, where
+ applicable. On backends where host visible staging buffers are used for
+ uploading, the staging buffers are kept around for this type, unlike with
+ Immutable, so subsequent uploads do not suffer in performance. Frequent
+ updates, especially updates in consecutive frames, should be avoided.
+
+ \value Dynamic Indicates that the data is expected to change frequently.
+ Not recommended for large buffers. Typically backed by host visible memory
+ in 2 copies in order to allow for changing without stalling the graphics
+ pipeline. The double buffering is managed transparently to the applications
+ and is not exposed in the API here in any form. This is the recommended,
+ and, with some backends, the only possible, type for buffers with
+ UniformBuffer usage.
+ */
+
+/*!
+ \enum QRhiBuffer::UsageFlag
+ Flag values to specify how the buffer is going to be used.
+
+ \value VertexBuffer Vertex buffer. This allows the QRhiBuffer to be used in
+ \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}.
+
+ \value IndexBuffer Index buffer. This allows the QRhiBuffer to be used in
+ \l{setVertexInput()}{QRhiCommandBuffer::setVertexInput()}.
+
+ \value UniformBuffer Uniform buffer (also called constant buffer). This
+ allows the QRhiBuffer to be used in combination with
+ \l{UniformBuffer}{QRhiShaderResourceBinding::UniformBuffer}. When
+ \l{QRhi::NonDynamicUniformBuffers}{NonDynamicUniformBuffers} is reported as
+ not supported, this usage can only be combined with the type Dynamic.
+
+ \value StorageBuffer Storage buffer. This allows the QRhiBuffer to be used
+ in combination with \l{BufferLoad}{QRhiShaderResourceBinding::BufferLoad},
+ \l{BufferStore}{QRhiShaderResourceBinding::BufferStore}, or
+ \l{BufferLoadStore}{QRhiShaderResourceBinding::BufferLoadStore}. This usage
+ can only be combined with the types Immutable or Static, and is only
+ available when the \l{QRhi::Compute}{Compute feature} is reported as
+ supported.
+ */
+
+/*!
+ \fn void QRhiBuffer::setSize(int sz)
+
+ Sets the size of the buffer in bytes. The size is normally specified in
+ QRhi::newBuffer() so this function is only used when the size has to be
+ changed. As with other setters, the size only takes effect when calling
+ build(), and for already built buffers this involves releasing the previous
+ native resource and creating new ones under the hood.
+
+ Backends may choose to allocate buffers bigger than \a sz in order to
+ fulfill alignment requirements. This is hidden from the applications and
+ size() will always report the size requested in \a sz.
+ */
+
+/*!
+ \internal
+ */
+QRhiBuffer::QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_)
+ : QRhiResource(rhi),
+ m_type(type_), m_usage(usage_), m_size(size_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiBuffer::resourceType() const
+{
+ return Buffer;
+}
+
+/*!
+ \fn bool QRhiBuffer::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiRenderBuffer
+ \inmodule QtRhi
+ \brief Renderbuffer resource.
+
+ Renderbuffers cannot be sampled or read but have some benefits over
+ textures in some cases:
+
+ A DepthStencil renderbuffer may be lazily allocated and be backed by
+ transient memory with some APIs. On some platforms this may mean the
+ depth/stencil buffer uses no physical backing at all.
+
+ Color renderbuffers are useful since QRhi::MultisampleRenderBuffer may be
+ supported even when QRhi::MultisampleTexture is not.
+
+ How the renderbuffer is implemented by a backend is not exposed to the
+ applications. In some cases it may be backed by ordinary textures, while in
+ others there may be a different kind of native resource used.
+ */
+
+/*!
+ \enum QRhiRenderBuffer::Type
+ Specifies the type of the renderbuffer
+
+ \value DepthStencil Combined depth/stencil
+ \value Color Color
+ */
+
+/*!
+ \enum QRhiRenderBuffer::Flag
+ Flag values for flags() and setFlags()
+
+ \value UsedWithSwapChainOnly For DepthStencil renderbuffers this indicates
+ that the renderbuffer is only used in combination with a QRhiSwapChain and
+ never in other ways. Relevant with some backends, while others ignore it.
+ With OpenGL where a separate windowing system interface API is in use (EGL,
+ GLX, etc.), the flag is important since it avoids creating any actual
+ resource as there is already a windowing system provided depth/stencil
+ buffer as requested by QSurfaceFormat.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderBuffer::QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_)
+ : QRhiResource(rhi),
+ m_type(type_), m_pixelSize(pixelSize_), m_sampleCount(sampleCount_), m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderBuffer::resourceType() const
+{
+ return RenderBuffer;
+}
+
+/*!
+ \fn bool QRhiRenderBuffer::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \fn QRhiTexture::Format QRhiRenderBuffer::backingFormat() const
+
+ \internal
+ */
+
+/*!
+ \class QRhiTexture
+ \inmodule QtRhi
+ \brief Texture resource.
+ */
+
+/*!
+ \enum QRhiTexture::Flag
+
+ Flag values to specify how the texture is going to be used. Not honoring
+ the flags set before build() and attempting to use the texture in ways that
+ was not declared upfront can lead to unspecified behavior or decreased
+ performance depending on the backend and the underlying graphics API.
+
+ \value RenderTarget The texture going to be used in combination with
+ QRhiTextureRenderTarget.
+
+ \value CubeMap The texture is a cubemap. Such textures have 6 layers, one
+ for each face in the order of +X, -X, +Y, -Y, +Z, -Z. Cubemap textures
+ cannot be multisample.
+
+ \value MipMapped The texture has mipmaps. The appropriate mip count is
+ calculated automatically and can also be retrieved via
+ QRhi::mipLevelsForSize(). The images for the mip levels have to be
+ provided in the texture uploaded or generated via
+ QRhiResourceUpdateBatch::generateMips(). Multisample textures cannot have
+ mipmaps.
+
+ \value sRGB Use an sRGB format.
+
+ \value UsedAsTransferSource The texture is used as the source of a texture
+ copy or readback, meaning the texture is given as the source in
+ QRhiResourceUpdateBatch::copyTexture() or
+ QRhiResourceUpdateBatch::readBackTexture().
+
+ \value UsedWithGenerateMips The texture is going to be used with
+ QRhiResourceUpdateBatch::generateMips().
+
+ \value UsedWithLoadStore The texture is going to be used with image
+ load/store operations, for example, in a compute shader.
+ */
+
+/*!
+ \enum QRhiTexture::Format
+
+ Specifies the texture format. See also QRhi::isTextureFormatSupported() and
+ note that flags() can modify the format when QRhiTexture::sRGB is set.
+
+ \value UnknownFormat Not a valid format. This cannot be passed to setFormat().
+
+ \value RGBA8 Four component, unsigned normalized 8 bit per component. Always supported.
+
+ \value BGRA8 Four component, unsigned normalized 8 bit per component.
+
+ \value R8 One component, unsigned normalized 8 bit.
+
+ \value R16 One component, unsigned normalized 16 bit.
+
+ \value RED_OR_ALPHA8 Either same as R8, or is a similar format with the component swizzled to alpha,
+ depending on \l{QRhi::RedOrAlpha8IsRed}{RedOrAlpha8IsRed}.
+
+ \value RGBA16F Four components, 16-bit float per component.
+
+ \value RGBA32F Four components, 32-bit float per component.
+
+ \value D16 16-bit depth (normalized unsigned integer)
+
+ \value D32F 32-bit depth (32-bit float)
+
+ \value BC1
+ \value BC2
+ \value BC3
+ \value BC4
+ \value BC5
+ \value BC6H
+ \value BC7
+
+ \value ETC2_RGB8
+ \value ETC2_RGB8A1
+ \value ETC2_RGBA8
+
+ \value ASTC_4x4
+ \value ASTC_5x4
+ \value ASTC_5x5
+ \value ASTC_6x5
+ \value ASTC_6x6
+ \value ASTC_8x5
+ \value ASTC_8x6
+ \value ASTC_8x8
+ \value ASTC_10x5
+ \value ASTC_10x6
+ \value ASTC_10x8
+ \value ASTC_10x10
+ \value ASTC_12x10
+ \value ASTC_12x12
+ */
+
+/*!
+ \internal
+ */
+QRhiTexture::QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_)
+ : QRhiResource(rhi),
+ m_format(format_), m_pixelSize(pixelSize_), m_sampleCount(sampleCount_), m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiTexture::resourceType() const
+{
+ return Texture;
+}
+
+/*!
+ \fn bool QRhiTexture::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \return a pointer to a backend-specific QRhiNativeHandles subclass, such as
+ QRhiVulkanTextureNativeHandles. The returned value is null when exposing
+ the underlying native resources is not supported by the backend.
+
+ \sa QRhiVulkanTextureNativeHandles, QRhiD3D11TextureNativeHandles,
+ QRhiMetalTextureNativeHandles, QRhiGles2TextureNativeHandles
+ */
+const QRhiNativeHandles *QRhiTexture::nativeHandles()
+{
+ return nullptr;
+}
+
+/*!
+ Similar to build() except that no new native textures are created. Instead,
+ the texture from \a src is used.
+
+ This allows importing an existing native texture object (which must belong
+ to the same device or sharing context, depending on the graphics API) from
+ an external graphics engine.
+
+ \note format(), pixelSize(), sampleCount(), and flags() must still be set
+ correctly. Passing incorrect sizes and other values to QRhi::newTexture()
+ and then following it with a buildFrom() expecting that the native texture
+ object alone is sufficient to deduce such values is \b wrong and will lead
+ to problems.
+
+ \note QRhiTexture does not take ownership of the texture object. release()
+ does not free the object or any associated memory.
+
+ The opposite of this operation, exposing a QRhiTexture-created native
+ texture object to a foreign engine, is possible via nativeHandles().
+
+ \sa QRhiVulkanTextureNativeHandles, QRhiD3D11TextureNativeHandles,
+ QRhiMetalTextureNativeHandles, QRhiGles2TextureNativeHandles
+ */
+bool QRhiTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ Q_UNUSED(src);
+ return false;
+}
+
+/*!
+ \class QRhiSampler
+ \inmodule QtRhi
+ \brief Sampler resource.
+ */
+
+/*!
+ \enum QRhiSampler::Filter
+ Specifies the minification, magnification, or mipmap filtering
+
+ \value None Applicable only for mipmapMode(), indicates no mipmaps to be used
+ \value Nearest
+ \value Linear
+ */
+
+/*!
+ \enum QRhiSampler::AddressMode
+ Specifies the addressing mode
+
+ \value Repeat
+ \value ClampToEdge
+ \value Border
+ \value Mirror
+ \value MirrorOnce
+ */
+
+/*!
+ \enum QRhiSampler::CompareOp
+ Specifies the texture comparison function.
+
+ \value Never (default)
+ \value Less
+ \value Equal
+ \value LessOrEqual
+ \value Greater
+ \value NotEqual
+ \value GreaterOrEqual
+ \value Always
+ */
+
+/*!
+ \internal
+ */
+QRhiSampler::QRhiSampler(QRhiImplementation *rhi,
+ Filter magFilter_, Filter minFilter_, Filter mipmapMode_,
+ AddressMode u_, AddressMode v_)
+ : QRhiResource(rhi),
+ m_magFilter(magFilter_), m_minFilter(minFilter_), m_mipmapMode(mipmapMode_),
+ m_addressU(u_), m_addressV(v_),
+ m_addressW(QRhiSampler::ClampToEdge),
+ m_compareOp(QRhiSampler::Never)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiSampler::resourceType() const
+{
+ return Sampler;
+}
+
+/*!
+ \class QRhiRenderPassDescriptor
+ \inmodule QtRhi
+ \brief Render pass resource.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderPassDescriptor::QRhiRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderPassDescriptor::resourceType() const
+{
+ return RenderPassDescriptor;
+}
+
+/*!
+ \class QRhiRenderTarget
+ \inmodule QtRhi
+ \brief Represents an onscreen (swapchain) or offscreen (texture) render target.
+ */
+
+/*!
+ \internal
+ */
+QRhiRenderTarget::QRhiRenderTarget(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiRenderTarget::resourceType() const
+{
+ return RenderTarget;
+}
+
+/*!
+ \fn QSize QRhiRenderTarget::pixelSize() const
+
+ \return the size in pixels.
+ */
+
+/*!
+ \fn float QRhiRenderTarget::devicePixelRatio() const
+
+ \return the device pixel ratio. For QRhiTextureRenderTarget this is always
+ 1. For targets retrieved from a QRhiSwapChain the value reflects the
+ \l{QWindow::devicePixelRatio()}{device pixel ratio} of the targeted
+ QWindow.
+ */
+
+/*!
+ \class QRhiTextureRenderTarget
+ \inmodule QtRhi
+ \brief Texture render target resource.
+
+ A texture render target allows rendering into one or more textures,
+ optionally with a depth texture or depth/stencil renderbuffer.
+
+ \note Textures used in combination with QRhiTextureRenderTarget must be
+ created with the QRhiTexture::RenderTarget flag.
+
+ The simplest example of creating a render target with a texture as its
+ single color attachment:
+
+ \badcode
+ texture = rhi->newTexture(QRhiTexture::RGBA8, size, 1, QRhiTexture::RenderTarget);
+ texture->build();
+ rt = rhi->newTextureRenderTarget({ texture });
+ rp = rt->newCompatibleRenderPassDescriptor();
+ rt->setRenderPassDescriptor(rt);
+ rt->build();
+ // rt can now be used with beginPass()
+ \endcode
+ */
+
+/*!
+ \enum QRhiTextureRenderTarget::Flag
+
+ Flag values describing the load/store behavior for the render target. The
+ load/store behavior may be baked into native resources under the hood,
+ depending on the backend, and therefore it needs to be known upfront and
+ cannot be changed without rebuilding (and so releasing and creating new
+ native resources).
+
+ \value PreserveColorContents Indicates that the contents of the color
+ attachments is to be loaded when starting a render pass, instead of
+ clearing. This is potentially more expensive, especially on mobile (tiled)
+ GPUs, but allows preserving the existing contents between passes.
+
+ \value PreserveDepthStencilContents Indicates that the contents of the
+ depth texture is to be loaded when starting a render pass, instead
+ clearing. Only applicable when a texture is used as the depth buffer
+ (QRhiTextureRenderTargetDescription::depthTexture() is set) because
+ depth/stencil renderbuffers may not have any physical backing and data may
+ not be written out in the first place.
+ */
+
+/*!
+ \internal
+ */
+QRhiTextureRenderTarget::QRhiTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc_,
+ Flags flags_)
+ : QRhiRenderTarget(rhi),
+ m_desc(desc_),
+ m_flags(flags_)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiTextureRenderTarget::resourceType() const
+{
+ return TextureRenderTarget;
+}
+
+/*!
+ \fn QRhiRenderPassDescriptor *QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor()
+
+ \return a new QRhiRenderPassDescriptor that is compatible with this render
+ target.
+
+ The returned value is used in two ways: it can be passed to
+ setRenderPassDescriptor() and
+ QRhiGraphicsPipeline::setRenderPassDescriptor(). A render pass descriptor
+ describes the attachments (color, depth/stencil) and the load/store
+ behavior that can be affected by flags(). A QRhiGraphicsPipeline can only
+ be used in combination with a render target that has the same
+ QRhiRenderPassDescriptor set.
+
+ Two QRhiTextureRenderTarget instances can share the same render pass
+ descriptor as long as they have the same number and type of attachments.
+ The associated QRhiTexture or QRhiRenderBuffer instances are not part of
+ the render pass descriptor so those can differ in the two
+ QRhiTextureRenderTarget intances.
+
+ \note resources, such as QRhiTexture instances, referenced in description()
+ must already be built
+
+ \sa build()
+ */
+
+/*!
+ \fn bool QRhiTextureRenderTarget::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \note renderPassDescriptor() must be set before calling build(). To obtain
+ a QRhiRenderPassDescriptor compatible with the render target, call
+ newCompatibleRenderPassDescriptor() before build() but after setting all
+ other parameters, such as description() and flags(). To save resources,
+ reuse the same QRhiRenderPassDescriptor with multiple
+ QRhiTextureRenderTarget instances, whenever possible. Sharing the same
+ render pass descriptor is only possible when the render targets have the
+ same number and type of attachments (the actual textures can differ) and
+ the same flags.
+
+ \note resources, such as QRhiTexture instances, referenced in description()
+ must already be built
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiShaderResourceBindings
+ \inmodule QtRhi
+ \brief Encapsulates resources for making buffer, texture, sampler resources visible to shaders.
+
+ A QRhiShaderResourceBindings is a collection of QRhiShaderResourceBinding
+ objects, each of which describe a single binding.
+
+ Take a fragment shader with the following interface:
+
+ \badcode
+ layout(std140, binding = 0) uniform buf {
+ mat4 mvp;
+ int flip;
+ } ubuf;
+
+ layout(binding = 1) uniform sampler2D tex;
+ \endcode
+
+ To make resources visible to the shader, the following
+ QRhiShaderResourceBindings could be created and then passed to
+ QRhiGraphicsPipeline::setShaderResourceBindings():
+
+ \badcode
+ srb = rhi->newShaderResourceBindings();
+ srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, ubuf),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler)
+ });
+ srb->build();
+ ...
+ ps = rhi->newGraphicsPipeline();
+ ...
+ ps->setShaderResourceBindings(srb);
+ ps->build();
+ ...
+ cb->setGraphicsPipeline(ps);
+ cb->setShaderResources(); // binds srb
+ \endcode
+
+ This assumes that \c ubuf is a QRhiBuffer, \c texture is a QRhiTexture,
+ while \a sampler is a QRhiSampler. The example also assumes that the
+ uniform block is present in the vertex shader as well so the same buffer is
+ made visible to the vertex stage too.
+
+ \section3 Advanced usage
+
+ Building on the above example, let's assume that a pass now needs to use
+ the exact same pipeline and shaders with a different texture. Creating a
+ whole separate QRhiGraphicsPipeline just for this would be an overkill.
+ This is why QRhiCommandBuffer::setShaderResources() allows specifying a \a
+ srb argument. As long as the layouts (so the number of bindings and the
+ binding points) match between two QRhiShaderResourceBindings, they can both
+ be used with the same pipeline, assuming the pipeline was built with one of
+ them in the first place.
+
+ Creating and then using a new \c srb2 that is very similar to \c srb with
+ the exception of referencing another texture could be implemented like the
+ following:
+
+ \badcode
+ srb2 = rhi->newShaderResourceBindings();
+ QVector<QRhiShaderResourceBinding> bindings = srb->bindings();
+ bindings[1] = QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, anotherTexture, sampler);
+ srb2->setBindings(bindings);
+ srb2->build();
+ ...
+ cb->setGraphicsPipeline(ps);
+ cb->setShaderResources(srb2); // binds srb2
+ \endcode
+ */
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBindings::QRhiShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiShaderResourceBindings::resourceType() const
+{
+ return ShaderResourceBindings;
+}
+
+/*!
+ \return \c true if the layout is compatible with \a other. The layout does
+ not include the actual resource (such as, buffer or texture) and related
+ parameters (such as, offset or size). It does include the binding point,
+ pipeline stage, and resource type, however. The number and order of the
+ bindings must also match in order to be compatible.
+
+ When there is a QRhiGraphicsPipeline created with this
+ QRhiShaderResourceBindings, and the function returns \c true, \a other can
+ then safely be passed to QRhiCommandBuffer::setShaderResources(), and so
+ be used with the pipeline in place of this QRhiShaderResourceBindings.
+
+ This function can be called before build() as well. The bindings must
+ already be set via setBindings() however.
+ */
+bool QRhiShaderResourceBindings::isLayoutCompatible(const QRhiShaderResourceBindings *other) const
+{
+ const int count = m_bindings.count();
+ if (count != other->m_bindings.count())
+ return false;
+
+ for (int i = 0; i < count; ++i) {
+ if (!m_bindings[i].isLayoutCompatible(other->m_bindings.at(i)))
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \class QRhiShaderResourceBinding
+ \inmodule QtRhi
+ \brief Describes the shader resource for a single binding point.
+
+ A QRhiShaderResourceBinding cannot be constructed directly. Instead, use
+ the static functions uniformBuffer(), sampledTexture() to get an instance.
+ */
+
+/*!
+ \enum QRhiShaderResourceBinding::Type
+ Specifies type of the shader resource bound to a binding point
+
+ \value UniformBuffer Uniform buffer
+
+ \value SampledTexture Combined image sampler
+
+ \value ImageLoad Image load (with GLSL this maps to doing imageLoad() on a
+ single level - and either one or all layers - of a texture exposed to the
+ shader as an image object)
+
+ \value ImageStore Image store (with GLSL this maps to doing imageStore() or
+ imageAtomic*() on a single level - and either one or all layers - of a
+ texture exposed to the shader as an image object)
+
+ \value ImageLoadStore Image load and store
+
+ \value BufferLoad Storage buffer store (with GLSL this maps to reading from
+ a shader storage buffer)
+
+ \value BufferStore Storage buffer store (with GLSL this maps to writing to
+ a shader storage buffer)
+
+ \value BufferLoadStore Storage buffer load and store
+ */
+
+/*!
+ \enum QRhiShaderResourceBinding::StageFlag
+ Flag values to indicate which stages the shader resource is visible in
+
+ \value VertexStage Vertex stage
+ \value FragmentStage Fragment (pixel) stage
+ \value ComputeStage Compute stage
+ */
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding::QRhiShaderResourceBinding()
+ : d(new QRhiShaderResourceBindingPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QRhiShaderResourceBinding::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding::QRhiShaderResourceBinding(const QRhiShaderResourceBinding &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QRhiShaderResourceBinding &QRhiShaderResourceBinding::operator=(const QRhiShaderResourceBinding &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QRhiShaderResourceBinding::~QRhiShaderResourceBinding()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return \c true if the layout is compatible with \a other. The layout does not
+ include the actual resource (such as, buffer or texture) and related
+ parameters (such as, offset or size).
+
+ For example, \c a and \c b below are not equal, but are compatible layout-wise:
+
+ \badcode
+ auto a = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, buffer);
+ auto b = QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, someOtherBuffer, 256);
+ \endcode
+ */
+bool QRhiShaderResourceBinding::isLayoutCompatible(const QRhiShaderResourceBinding &other) const
+{
+ return (d == other.d)
+ || (d->binding == other.d->binding && d->stage == other.d->stage && d->type == other.d->type);
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf.
+
+ \note \a buf must have been created with QRhiBuffer::UniformBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer(
+ int binding, StageFlags stage, QRhiBuffer *buf)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.loadRelaxed() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = UniformBuffer;
+ d->u.ubuf.buf = buf;
+ d->u.ubuf.offset = 0;
+ d->u.ubuf.maybeSize = 0; // entire buffer
+ d->u.ubuf.hasDynamicOffset = false;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf. This
+ overload binds a region only, as specified by \a offset and \a size.
+
+ \note It is up to the user to ensure the offset is aligned to
+ QRhi::ubufAlignment().
+
+ \note \a size must be greater than 0.
+
+ \note \a buf must have been created with QRhiBuffer::UniformBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBuffer(
+ int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size)
+{
+ Q_ASSERT(size > 0);
+ QRhiShaderResourceBinding b = uniformBuffer(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->u.ubuf.offset = offset;
+ d->u.ubuf.maybeSize = size;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, and buffer specified by \a binding, \a stage, and \a buf. The
+ uniform buffer is assumed to have dynamic offset. The dynamic offset can be
+ specified in QRhiCommandBuffer::setShaderResources(), thus allowing using
+ varying offset values without creating new bindings for the buffer. The
+ size of the bound region is specified by \a size. Like with non-dynamic
+ offsets, \c{offset + size} cannot exceed the size of \a buf.
+
+ \note \a buf must have been created with QRhiBuffer::UniformBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(
+ int binding, StageFlags stage, QRhiBuffer *buf, int size)
+{
+ QRhiShaderResourceBinding b = uniformBuffer(binding, stage, buf, 0, size);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->u.ubuf.hasDynamicOffset = true;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for the given binding number, pipeline
+ stages, texture, and sampler specified by \a binding, \a stage, \a tex,
+ \a sampler.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::sampledTexture(
+ int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.loadRelaxed() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = SampledTexture;
+ d->u.stex.tex = tex;
+ d->u.stex.sampler = sampler;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read-only storage image with the
+ given \a binding number and pipeline \a stage. The image load operations
+ will have access to all layers of the specified \a level. (so if the texture
+ is a cubemap, the shader must use imageCube instead of image2D)
+
+ \note \a tex must have been created with QRhiTexture::UsedWithLoadStore.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoad(
+ int binding, StageFlags stage, QRhiTexture *tex, int level)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.loadRelaxed() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = ImageLoad;
+ d->u.simage.tex = tex;
+ d->u.simage.level = level;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a write-only storage image with the
+ given \a binding number and pipeline \a stage. The image store operations
+ will have access to all layers of the specified \a level. (so if the texture
+ is a cubemap, the shader must use imageCube instead of image2D)
+
+ \note \a tex must have been created with QRhiTexture::UsedWithLoadStore.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::imageStore(
+ int binding, StageFlags stage, QRhiTexture *tex, int level)
+{
+ QRhiShaderResourceBinding b = imageLoad(binding, stage, tex, level);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->type = ImageStore;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read/write storage image with the
+ given \a binding number and pipeline \a stage. The image load/store operations
+ will have access to all layers of the specified \a level. (so if the texture
+ is a cubemap, the shader must use imageCube instead of image2D)
+
+ \note \a tex must have been created with QRhiTexture::UsedWithLoadStore.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::imageLoadStore(
+ int binding, StageFlags stage, QRhiTexture *tex, int level)
+{
+ QRhiShaderResourceBinding b = imageLoad(binding, stage, tex, level);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->type = ImageLoadStore;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read-only storage buffer with the
+ given \a binding number and pipeline \a stage.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad(
+ int binding, StageFlags stage, QRhiBuffer *buf)
+{
+ QRhiShaderResourceBinding b;
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ Q_ASSERT(d->ref.loadRelaxed() == 1);
+ d->binding = binding;
+ d->stage = stage;
+ d->type = BufferLoad;
+ d->u.sbuf.buf = buf;
+ d->u.sbuf.offset = 0;
+ d->u.sbuf.maybeSize = 0; // entire buffer
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read-only storage buffer with the
+ given \a binding number and pipeline \a stage. This overload binds a region
+ only, as specified by \a offset and \a size.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoad(
+ int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size)
+{
+ Q_ASSERT(size > 0);
+ QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->u.sbuf.offset = offset;
+ d->u.sbuf.maybeSize = size;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a write-only storage buffer with the
+ given \a binding number and pipeline \a stage.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore(
+ int binding, StageFlags stage, QRhiBuffer *buf)
+{
+ QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->type = BufferStore;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a write-only storage buffer with the
+ given \a binding number and pipeline \a stage. This overload binds a region
+ only, as specified by \a offset and \a size.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferStore(
+ int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size)
+{
+ Q_ASSERT(size > 0);
+ QRhiShaderResourceBinding b = bufferStore(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->u.sbuf.offset = offset;
+ d->u.sbuf.maybeSize = size;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read-write storage buffer with the
+ given \a binding number and pipeline \a stage.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore(
+ int binding, StageFlags stage, QRhiBuffer *buf)
+{
+ QRhiShaderResourceBinding b = bufferLoad(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->type = BufferLoadStore;
+ return b;
+}
+
+/*!
+ \return a shader resource binding for a read-write storage buffer with the
+ given \a binding number and pipeline \a stage. This overload binds a region
+ only, as specified by \a offset and \a size.
+
+ \note \a buf must have been created with QRhiBuffer::StorageBuffer.
+ */
+QRhiShaderResourceBinding QRhiShaderResourceBinding::bufferLoadStore(
+ int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size)
+{
+ Q_ASSERT(size > 0);
+ QRhiShaderResourceBinding b = bufferLoadStore(binding, stage, buf);
+ QRhiShaderResourceBindingPrivate *d = QRhiShaderResourceBindingPrivate::get(&b);
+ d->u.sbuf.offset = offset;
+ d->u.sbuf.maybeSize = size;
+ return b;
+}
+
+/*!
+ \return \c true if the contents of the two QRhiShaderResourceBinding
+ objects \a a and \a b are equal. This includes the resources (buffer,
+ texture) and related parameters (offset, size) as well. To only compare
+ layouts (binding point, pipeline stage, resource type), use
+ \l{QRhiShaderResourceBinding::isLayoutCompatible()}{isLayoutCompatible()}
+ instead.
+
+ \relates QRhiShaderResourceBinding
+ */
+bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
+{
+ if (a.d == b.d)
+ return true;
+
+ if (a.d->binding != b.d->binding
+ || a.d->stage != b.d->stage
+ || a.d->type != b.d->type)
+ {
+ return false;
+ }
+
+ switch (a.d->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (a.d->u.ubuf.buf != b.d->u.ubuf.buf
+ || a.d->u.ubuf.offset != b.d->u.ubuf.offset
+ || a.d->u.ubuf.maybeSize != b.d->u.ubuf.maybeSize)
+ {
+ return false;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ if (a.d->u.stex.tex != b.d->u.stex.tex
+ || a.d->u.stex.sampler != b.d->u.stex.sampler)
+ {
+ return false;
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ if (a.d->u.simage.tex != b.d->u.simage.tex
+ || a.d->u.simage.level != b.d->u.simage.level)
+ {
+ return false;
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ if (a.d->u.sbuf.buf != b.d->u.sbuf.buf
+ || a.d->u.sbuf.offset != b.d->u.sbuf.offset
+ || a.d->u.sbuf.maybeSize != b.d->u.sbuf.maybeSize)
+ {
+ return false;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \return \c false if all the bindings in the two QRhiShaderResourceBinding
+ objects \a a and \a b are equal; otherwise returns \c true.
+
+ \relates QRhiShaderResourceBinding
+ */
+bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW
+{
+ return !(a == b);
+}
+
+/*!
+ \return the hash value for \a b, using \a seed to seed the calculation.
+
+ \relates QRhiShaderResourceBinding
+ */
+uint qHash(const QRhiShaderResourceBinding &b, uint seed) Q_DECL_NOTHROW
+{
+ const char *u = reinterpret_cast<const char *>(&b.d->u);
+ return seed + b.d->binding + 10 * b.d->stage + 100 * b.d->type
+ + qHash(QByteArray::fromRawData(u, sizeof(b.d->u)), seed);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiShaderResourceBinding &b)
+{
+ const QRhiShaderResourceBindingPrivate *d = b.d;
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiShaderResourceBinding("
+ << "binding=" << d->binding
+ << " stage=" << d->stage
+ << " type=" << d->type;
+ switch (d->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ dbg.nospace() << " UniformBuffer("
+ << "buffer=" << d->u.ubuf.buf
+ << " offset=" << d->u.ubuf.offset
+ << " maybeSize=" << d->u.ubuf.maybeSize
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ dbg.nospace() << " SampledTexture("
+ << "texture=" << d->u.stex.tex
+ << " sampler=" << d->u.stex.sampler
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ dbg.nospace() << " ImageLoad("
+ << "texture=" << d->u.simage.tex
+ << " level=" << d->u.simage.level
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::ImageStore:
+ dbg.nospace() << " ImageStore("
+ << "texture=" << d->u.simage.tex
+ << " level=" << d->u.simage.level
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ dbg.nospace() << " ImageLoadStore("
+ << "texture=" << d->u.simage.tex
+ << " level=" << d->u.simage.level
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ dbg.nospace() << " BufferLoad("
+ << "buffer=" << d->u.sbuf.buf
+ << " offset=" << d->u.sbuf.offset
+ << " maybeSize=" << d->u.sbuf.maybeSize
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::BufferStore:
+ dbg.nospace() << " BufferStore("
+ << "buffer=" << d->u.sbuf.buf
+ << " offset=" << d->u.sbuf.offset
+ << " maybeSize=" << d->u.sbuf.maybeSize
+ << ')';
+ break;
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ dbg.nospace() << " BufferLoadStore("
+ << "buffer=" << d->u.sbuf.buf
+ << " offset=" << d->u.sbuf.offset
+ << " maybeSize=" << d->u.sbuf.maybeSize
+ << ')';
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ dbg.nospace() << ')';
+ return dbg;
+}
+#endif
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "QRhiShaderResourceBindings("
+ << srb.m_bindings
+ << ')';
+ return dbg;
+}
+#endif
+
+/*!
+ \class QRhiGraphicsPipeline
+ \inmodule QtRhi
+ \brief Graphics pipeline state resource.
+
+ \note Setting the shader resource bindings is mandatory. The referenced
+ QRhiShaderResourceBindings must already be built by the time build() is
+ called.
+
+ \note Setting the render pass descriptor is mandatory. To obtain a
+ QRhiRenderPassDescriptor that can be passed to setRenderPassDescriptor(),
+ use either QRhiTextureRenderTarget::newCompatibleRenderPassDescriptor() or
+ QRhiSwapChain::newCompatibleRenderPassDescriptor().
+
+ \note Setting the vertex input layout is mandatory.
+
+ \note Setting the shader stages is mandatory.
+
+ \note sampleCount() defaults to 1 and must match the sample count of the
+ render target's color and depth stencil attachments.
+
+ \note The depth test, depth write, and stencil test are disabled by
+ default.
+
+ \note stencilReadMask() and stencilWriteMask() apply to both faces. They
+ both default to 0xFF.
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setTargetBlends(const QVector<TargetBlend> &blends)
+
+ Sets the blend specification for color attachments. Each element in \a
+ blends corresponds to a color attachment of the render target.
+
+ By default no blends are set, which is a shortcut to disabling blending and
+ enabling color write for all four channels.
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::Flag
+
+ Flag values for describing the dynamic state of the pipeline. The viewport is always dynamic.
+
+ \value UsesBlendConstants Indicates that a blend color constant will be set
+ via QRhiCommandBuffer::setBlendConstants()
+
+ \value UsesStencilRef Indicates that a stencil reference value will be set
+ via QRhiCommandBuffer::setStencilRef()
+
+ \value UsesScissor Indicates that a scissor rectangle will be set via
+ QRhiCommandBuffer::setScissor()
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::Topology
+ Specifies the primitive topology
+
+ \value Triangles (default)
+ \value TriangleStrip
+ \value Lines
+ \value LineStrip
+ \value Points
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::CullMode
+ Specifies the culling mode
+
+ \value None No culling (default)
+ \value Front Cull front faces
+ \value Back Cull back faces
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::FrontFace
+ Specifies the front face winding order
+
+ \value CCW Counter clockwise (default)
+ \value CW Clockwise
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::ColorMaskComponent
+ Flag values for specifying the color write mask
+
+ \value R
+ \value G
+ \value B
+ \value A
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::BlendFactor
+ Specifies the blend factor
+
+ \value Zero
+ \value One
+ \value SrcColor
+ \value OneMinusSrcColor
+ \value DstColor
+ \value OneMinusDstColor
+ \value SrcAlpha
+ \value OneMinusSrcAlpha
+ \value DstAlpha
+ \value OneMinusDstAlpha
+ \value ConstantColor
+ \value OneMinusConstantColor
+ \value ConstantAlpha
+ \value OneMinusConstantAlpha
+ \value SrcAlphaSaturate
+ \value Src1Color
+ \value OneMinusSrc1Color
+ \value Src1Alpha
+ \value OneMinusSrc1Alpha
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::BlendOp
+ Specifies the blend operation
+
+ \value Add
+ \value Subtract
+ \value ReverseSubtract
+ \value Min
+ \value Max
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::CompareOp
+ Specifies the depth or stencil comparison function
+
+ \value Never
+ \value Less (default for depth)
+ \value Equal
+ \value LessOrEqual
+ \value Greater
+ \value NotEqual
+ \value GreaterOrEqual
+ \value Always (default for stencil)
+ */
+
+/*!
+ \enum QRhiGraphicsPipeline::StencilOp
+ Specifies the stencil operation
+
+ \value StencilZero
+ \value Keep (default)
+ \value Replace
+ \value IncrementAndClamp
+ \value DecrementAndClamp
+ \value Invert
+ \value IncrementAndWrap
+ \value DecrementAndWrap
+ */
+
+/*!
+ \class QRhiGraphicsPipeline::TargetBlend
+ \inmodule QtRhi
+ \brief Describes the blend state for one color attachment.
+
+ Defaults to color write enabled, blending disabled. The blend values are
+ set up for pre-multiplied alpha (One, OneMinusSrcAlpha, One,
+ OneMinusSrcAlpha) by default.
+ */
+
+/*!
+ \class QRhiGraphicsPipeline::StencilOpState
+ \inmodule QtRhi
+ \brief Describes the stencil operation state.
+ */
+
+/*!
+ \internal
+ */
+QRhiGraphicsPipeline::QRhiGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
+{
+ return GraphicsPipeline;
+}
+
+/*!
+ \fn bool QRhiGraphicsPipeline::build()
+
+ Creates the corresponding native graphics resources. If there are already
+ resources present due to an earlier build() with no corresponding
+ release(), then release() is called implicitly first.
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthTest(bool enable)
+
+ Enables or disables depth testing. Both depth test and the writing out of
+ depth data are disabled by default.
+
+ \sa setDepthWrite()
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthWrite(bool enable)
+
+ Controls the writing out of depth data into the depth buffer. By default
+ this is disabled. Depth write is typically enabled together with the depth
+ test.
+
+ \note Enabling depth write without having depth testing enabled may not
+ lead to the desired result, and should be avoided.
+
+ \sa setDepthTest()
+ */
+
+/*!
+ \class QRhiSwapChain
+ \inmodule QtRhi
+ \brief Swapchain resource.
+
+ A swapchain enables presenting rendering results to a surface. A swapchain
+ is typically backed by a set of color buffers. Of these, one is displayed
+ at a time.
+
+ Below is a typical pattern for creating and managing a swapchain and some
+ associated resources in order to render onto a QWindow:
+
+ \badcode
+ void init()
+ {
+ sc = rhi->newSwapChain();
+ ds = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
+ QSize(), // no need to set the size yet
+ 1,
+ QRhiRenderBuffer::UsedWithSwapChainOnly);
+ sc->setWindow(window);
+ sc->setDepthStencil(ds);
+ rp = sc->newCompatibleRenderPassDescriptor();
+ sc->setRenderPassDescriptor(rp);
+ resizeSwapChain();
+ }
+
+ void resizeSwapChain()
+ {
+ const QSize outputSize = sc->surfacePixelSize();
+ ds->setPixelSize(outputSize);
+ ds->build();
+ hasSwapChain = sc->buildOrResize();
+ }
+
+ void render()
+ {
+ if (!hasSwapChain || notExposed)
+ return;
+
+ if (sc->currentPixelSize() != sc->surfacePixelSize() || newlyExposed) {
+ resizeSwapChain();
+ if (!hasSwapChain)
+ return;
+ newlyExposed = false;
+ }
+
+ rhi->beginFrame(sc);
+ // ...
+ rhi->endFrame(sc);
+ }
+ \endcode
+
+ Avoid relying on QWindow resize events to resize swapchains, especially
+ considering that surface sizes may not always fully match the QWindow
+ reported dimensions. The safe, cross-platform approach is to do the check
+ via surfacePixelSize() whenever starting a new frame.
+
+ Releasing the swapchain must happen while the QWindow and the underlying
+ native window is fully up and running. Building on the previous example:
+
+ \badcode
+ void releaseSwapChain()
+ {
+ if (hasSwapChain) {
+ sc->release();
+ hasSwapChain = false;
+ }
+ }
+
+ // assuming Window is our QWindow subclass
+ bool Window::event(QEvent *e)
+ {
+ switch (e->type()) {
+ case QEvent::UpdateRequest: // for QWindow::requestUpdate()
+ render();
+ break;
+ case QEvent::PlatformSurface:
+ if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ releaseSwapChain();
+ break;
+ default:
+ break;
+ }
+ return QWindow::event(e);
+ }
+ \endcode
+
+ Initializing the swapchain and starting to render the first frame cannot
+ start at any time. The safe, cross-platform approach is to rely on expose
+ events. QExposeEvent is a loosely specified event that is sent whenever a
+ window gets mapped, obscured, and resized, depending on the platform.
+
+ \badcode
+ void Window::exposeEvent(QExposeEvent *)
+ {
+ // initialize and start rendering when the window becomes usable for graphics purposes
+ if (isExposed() && !running) {
+ running = true;
+ init();
+ }
+
+ // stop pushing frames when not exposed or size becomes 0
+ if ((!isExposed() || (hasSwapChain && sc->surfacePixelSize().isEmpty())) && running)
+ notExposed = true;
+
+ // continue when exposed again and the surface has a valid size
+ if (isExposed() && running && notExposed && !sc->surfacePixelSize().isEmpty()) {
+ notExposed = false;
+ newlyExposed = true;
+ }
+
+ if (isExposed() && !sc->surfacePixelSize().isEmpty())
+ render();
+ }
+ \endcode
+
+ Once the rendering has started, a simple way to request a new frame is
+ QWindow::requestUpdate(). While on some platforms this is merely a small
+ timer, on others it has a specific implementation: for instance on macOS or
+ iOS it may be backed by
+ \l{https://developer.apple.com/documentation/corevideo/cvdisplaylink?language=objc}{CVDisplayLink}.
+ The example above is already prepared for update requests by handling
+ QEvent::UpdateRequest.
+
+ While acting as a QRhiRenderTarget, QRhiSwapChain also manages a
+ QRhiCommandBuffer. Calling QRhi::endFrame() submits the recorded commands
+ and also enqueues a \c present request. The default behavior is to do this
+ with a swap interval of 1, meaning synchronizing to the display's vertical
+ refresh is enabled. Thus the rendering thread calling beginFrame() and
+ endFrame() will get throttled to vsync. On some backends this can be
+ disabled by passing QRhiSwapChain:NoVSync in flags().
+
+ Multisampling (MSAA) is handled transparently to the applications when
+ requested via setSampleCount(). Where applicable, QRhiSwapChain will take
+ care of creating additional color buffers and issuing a multisample resolve
+ command at the end of a frame. For OpenGL, it is necessary to request the
+ appropriate sample count also via QSurfaceFormat, by calling
+ QSurfaceFormat::setDefaultFormat() before initializing the QRhi.
+ */
+
+/*!
+ \enum QRhiSwapChain::Flag
+ Flag values to describe swapchain properties
+
+ \value SurfaceHasPreMulAlpha Indicates that the target surface has
+ transparency with premultiplied alpha.
+
+ \value SurfaceHasNonPreMulAlpha Indicates the target surface has
+ transparencyt with non-premultiplied alpha.
+
+ \value sRGB Requests to pick an sRGB format for the swapchain and/or its
+ render target views, where applicable. Note that this implies that sRGB
+ framebuffer update and blending will get enabled for all content targeting
+ this swapchain, and opting out is not possible. For OpenGL, set
+ \l{QSurfaceFormat::sRGBColorSpace}{sRGBColorSpace} on the QSurfaceFormat of
+ the QWindow in addition.
+
+ \value UsedAsTransferSource Indicates the the swapchain will be used as the
+ source of a readback in QRhiResourceUpdateBatch::readBackTexture().
+
+ \value NoVSync Requests disabling waiting for vertical sync, also avoiding
+ throttling the rendering thread. The behavior is backend specific and
+ applicable only where it is possible to control this. Some may ignore the
+ request altogether. For OpenGL, try instead setting the swap interval to 0
+ on the QWindow via QSurfaceFormat::setSwapInterval().
+
+ \value MinimalBufferCount Requests creating the swapchain with the minimum
+ number of buffers, which is in practice 2, unless the graphics
+ implementation has a higher minimum number than that. Only applicable with
+ backends where such control is available via the graphics API, for example,
+ Vulkan. By default it is up to the backend to decide what number of buffers
+ it requests (in practice this is almost always either 2 or 3), and it is
+ not the applications' concern. However, on Vulkan for instance the backend
+ will likely prefer the higher number (3), for example to avoid odd
+ performance issues with some Vulkan implementations on mobile devices. It
+ could be that on some platforms it can prove to be beneficial to force the
+ lower buffer count (2), so this flag allows forcing that. Note that all
+ this has no effect on the number of frames kept in flight, so the CPU
+ (QRhi) will still prepare frames at most \c{N - 1} frames ahead of the GPU,
+ even when the swapchain image buffer count larger than \c N. (\c{N} =
+ QRhi::FramesInFlight and typically 2).
+ */
+
+/*!
+ \internal
+ */
+QRhiSwapChain::QRhiSwapChain(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiSwapChain::resourceType() const
+{
+ return SwapChain;
+}
+
+/*!
+ \fn QSize QRhiSwapChain::currentPixelSize() const
+
+ \return the size with which the swapchain was last successfully built. Use
+ this to decide if buildOrResize() needs to be called again: if
+ \c{currentPixelSize() != surfacePixelSize()} then the swapchain needs to be
+ resized.
+
+ \sa surfacePixelSize()
+ */
+
+/*!
+ \fn QSize QRhiSwapChain::surfacePixelSize()
+
+ \return The size of the window's associated surface or layer. Do not assume
+ this is the same as QWindow::size() * QWindow::devicePixelRatio().
+
+ Can be called before buildOrResize() (but with window() already set), which
+ allows setting the correct size for the depth-stencil buffer that is then
+ used together with the swapchain's color buffers. Also used in combination
+ with currentPixelSize() to detect size changes.
+
+ \sa currentPixelSize()
+ */
+
+/*!
+ \fn QRhiCommandBuffer *QRhiSwapChain::currentFrameCommandBuffer()
+
+ \return a command buffer on which rendering commands can be recorded. Only
+ valid within a QRhi::beginFrame() - QRhi::endFrame() block where
+ beginFrame() was called with this swapchain.
+
+ \note the value must not be cached and reused between frames
+*/
+
+/*!
+ \fn QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget()
+
+ \return a render target that can used with beginPass() in order to render
+ the the swapchain's current backbuffer. Only valid within a
+ QRhi::beginFrame() - QRhi::endFrame() block where beginFrame() was called
+ with this swapchain.
+
+ \note the value must not be cached and reused between frames
+ */
+
+/*!
+ \fn bool QRhiSwapChain::buildOrResize()
+
+ Creates the swapchain if not already done and resizes the swapchain buffers
+ to match the current size of the targeted surface. Call this whenever the
+ size of the target surface is different than before.
+
+ \note call release() only when the swapchain needs to be released
+ completely, typically upon
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed. To perform resizing, just
+ call buildOrResize().
+
+ \return \c true when successful, \c false when a graphics operation failed.
+ Regardless of the return value, calling release() is always safe.
+ */
+
+/*!
+ \class QRhiComputePipeline
+ \inmodule QtRhi
+ \brief Compute pipeline state resource.
+
+ \note Setting the shader resource bindings is mandatory. The referenced
+ QRhiShaderResourceBindings must already be built by the time build() is
+ called.
+
+ \note Setting the shader is mandatory.
+ */
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiComputePipeline::resourceType() const
+{
+ return ComputePipeline;
+}
+
+/*!
+ \internal
+ */
+QRhiComputePipeline::QRhiComputePipeline(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \class QRhiCommandBuffer
+ \inmodule QtRhi
+ \brief Command buffer resource.
+
+ Not creatable by applications at the moment. The only ways to obtain a
+ valid QRhiCommandBuffer are to get it from the targeted swapchain via
+ QRhiSwapChain::currentFrameCommandBuffer(), or, in case of rendering
+ completely offscreen, initializing one via QRhi::beginOffscreenFrame().
+ */
+
+/*!
+ \enum QRhiCommandBuffer::IndexFormat
+ Specifies the index data type
+
+ \value IndexUInt16 Unsigned 16-bit (quint16)
+ \value IndexUInt32 Unsigned 32-bit (quint32)
+ */
+
+/*!
+ \typedef QRhiCommandBuffer::DynamicOffset
+
+ Synonym for QPair<int, quint32>. The first entry is the binding, the second
+ is the offset in the buffer.
+*/
+
+/*!
+ \typedef QRhiCommandBuffer::VertexInput
+
+ Synonym for QPair<QRhiBuffer *, quint32>. The second entry is an offset in
+ the buffer specified by the first.
+*/
+
+/*!
+ \internal
+ */
+QRhiCommandBuffer::QRhiCommandBuffer(QRhiImplementation *rhi)
+ : QRhiResource(rhi)
+{
+}
+
+/*!
+ \return the resource type.
+ */
+QRhiResource::Type QRhiCommandBuffer::resourceType() const
+{
+ return CommandBuffer;
+}
+
+QRhiImplementation::~QRhiImplementation()
+{
+ qDeleteAll(resUpdPool);
+
+ // Be nice and show something about leaked stuff. Though we may not get
+ // this far with some backends where the allocator or the api may check
+ // and freak out for unfreed graphics objects in the derived dtor already.
+#ifndef QT_NO_DEBUG
+ if (!resources.isEmpty()) {
+ qWarning("QRhi %p going down with %d unreleased resources. This is not nice.",
+ q, resources.count());
+ for (QRhiResource *res : qAsConst(resources)) {
+ qWarning(" Resource %p (%s)", res, res->m_objectName.constData());
+ res->m_rhi = nullptr;
+ }
+ }
+#endif
+}
+
+bool QRhiImplementation::isCompressedFormat(QRhiTexture::Format format) const
+{
+ return (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
+ || (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
+ || (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12);
+}
+
+void QRhiImplementation::compressedFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize,
+ QSize *blockDim) const
+{
+ int xdim = 4;
+ int ydim = 4;
+ quint32 blockSize = 0;
+
+ switch (format) {
+ case QRhiTexture::BC1:
+ blockSize = 8;
+ break;
+ case QRhiTexture::BC2:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC3:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC4:
+ blockSize = 8;
+ break;
+ case QRhiTexture::BC5:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC6H:
+ blockSize = 16;
+ break;
+ case QRhiTexture::BC7:
+ blockSize = 16;
+ break;
+
+ case QRhiTexture::ETC2_RGB8:
+ blockSize = 8;
+ break;
+ case QRhiTexture::ETC2_RGB8A1:
+ blockSize = 8;
+ break;
+ case QRhiTexture::ETC2_RGBA8:
+ blockSize = 16;
+ break;
+
+ case QRhiTexture::ASTC_4x4:
+ blockSize = 16;
+ break;
+ case QRhiTexture::ASTC_5x4:
+ blockSize = 16;
+ xdim = 5;
+ break;
+ case QRhiTexture::ASTC_5x5:
+ blockSize = 16;
+ xdim = ydim = 5;
+ break;
+ case QRhiTexture::ASTC_6x5:
+ blockSize = 16;
+ xdim = 6;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_6x6:
+ blockSize = 16;
+ xdim = ydim = 6;
+ break;
+ case QRhiTexture::ASTC_8x5:
+ blockSize = 16;
+ xdim = 8;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_8x6:
+ blockSize = 16;
+ xdim = 8;
+ ydim = 6;
+ break;
+ case QRhiTexture::ASTC_8x8:
+ blockSize = 16;
+ xdim = ydim = 8;
+ break;
+ case QRhiTexture::ASTC_10x5:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 5;
+ break;
+ case QRhiTexture::ASTC_10x6:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 6;
+ break;
+ case QRhiTexture::ASTC_10x8:
+ blockSize = 16;
+ xdim = 10;
+ ydim = 8;
+ break;
+ case QRhiTexture::ASTC_10x10:
+ blockSize = 16;
+ xdim = ydim = 10;
+ break;
+ case QRhiTexture::ASTC_12x10:
+ blockSize = 16;
+ xdim = 12;
+ ydim = 10;
+ break;
+ case QRhiTexture::ASTC_12x12:
+ blockSize = 16;
+ xdim = ydim = 12;
+ break;
+
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ const quint32 wblocks = (size.width() + xdim - 1) / xdim;
+ const quint32 hblocks = (size.height() + ydim - 1) / ydim;
+
+ if (bpl)
+ *bpl = wblocks * blockSize;
+ if (byteSize)
+ *byteSize = wblocks * hblocks * blockSize;
+ if (blockDim)
+ *blockDim = QSize(xdim, ydim);
+}
+
+void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize) const
+{
+ if (isCompressedFormat(format)) {
+ compressedFormatInfo(format, size, bpl, byteSize, nullptr);
+ return;
+ }
+
+ quint32 bpc = 0;
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ bpc = 4;
+ break;
+ case QRhiTexture::BGRA8:
+ bpc = 4;
+ break;
+ case QRhiTexture::R8:
+ bpc = 1;
+ break;
+ case QRhiTexture::R16:
+ bpc = 2;
+ break;
+ case QRhiTexture::RED_OR_ALPHA8:
+ bpc = 1;
+ break;
+
+ case QRhiTexture::RGBA16F:
+ bpc = 8;
+ break;
+ case QRhiTexture::RGBA32F:
+ bpc = 16;
+ break;
+
+ case QRhiTexture::D16:
+ bpc = 2;
+ break;
+ case QRhiTexture::D32F:
+ bpc = 4;
+ break;
+
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ if (bpl)
+ *bpl = size.width() * bpc;
+ if (byteSize)
+ *byteSize = size.width() * size.height() * bpc;
+}
+
+// Approximate because it excludes subresource alignment or multisampling.
+quint32 QRhiImplementation::approxByteSizeForTexture(QRhiTexture::Format format, const QSize &baseSize,
+ int mipCount, int layerCount)
+{
+ quint32 approxSize = 0;
+ for (int level = 0; level < mipCount; ++level) {
+ quint32 byteSize = 0;
+ const QSize size(qFloor(float(qMax(1, baseSize.width() >> level))),
+ qFloor(float(qMax(1, baseSize.height() >> level))));
+ textureFormatInfo(format, size, nullptr, &byteSize);
+ approxSize += byteSize;
+ }
+ approxSize *= layerCount;
+ return approxSize;
+}
+
+/*!
+ \internal
+ */
+QRhi::QRhi()
+{
+}
+
+/*!
+ Destructor. Destroys the backend and releases resources.
+ */
+QRhi::~QRhi()
+{
+ if (!d)
+ return;
+
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ runCleanup();
+
+ d->destroy();
+ delete d;
+}
+
+/*!
+ \return a new QRhi instance with a backend for the graphics API specified by \a impl.
+
+ \a params must point to an instance of one of the backend-specific
+ subclasses of QRhiInitParams, such as, QRhiVulkanInitParams,
+ QRhiMetalInitParams, QRhiD3D11InitParams, QRhiGles2InitParams. See these
+ classes for examples on creating a QRhi.
+
+ \a flags is optional. It is used to enable profile and debug related
+ features that are potentially expensive and should only be used during
+ development.
+ */
+QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRhiNativeHandles *importDevice)
+{
+ QScopedPointer<QRhi> r(new QRhi);
+
+ switch (impl) {
+ case Null:
+ r->d = new QRhiNull(static_cast<QRhiNullInitParams *>(params));
+ break;
+ case Vulkan:
+#if QT_CONFIG(vulkan)
+ r->d = new QRhiVulkan(static_cast<QRhiVulkanInitParams *>(params),
+ static_cast<QRhiVulkanNativeHandles *>(importDevice));
+ break;
+#else
+ Q_UNUSED(importDevice);
+ qWarning("This build of Qt has no Vulkan support");
+ break;
+#endif
+ case OpenGLES2:
+#ifndef QT_NO_OPENGL
+ r->d = new QRhiGles2(static_cast<QRhiGles2InitParams *>(params),
+ static_cast<QRhiGles2NativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This build of Qt has no OpenGL support");
+ break;
+#endif
+ case D3D11:
+#ifdef Q_OS_WIN
+ r->d = new QRhiD3D11(static_cast<QRhiD3D11InitParams *>(params),
+ static_cast<QRhiD3D11NativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This platform has no Direct3D 11 support");
+ break;
+#endif
+ case Metal:
+//#ifdef Q_OS_DARWIN
+#ifdef Q_OS_MACOS
+ r->d = new QRhiMetal(static_cast<QRhiMetalInitParams *>(params),
+ static_cast<QRhiMetalNativeHandles *>(importDevice));
+ break;
+#else
+ qWarning("This platform has no Metal support");
+ break;
+#endif
+ default:
+ break;
+ }
+
+ if (r->d) {
+ r->d->q = r.data();
+ if (flags.testFlag(EnableProfiling)) {
+ QRhiProfilerPrivate *profD = QRhiProfilerPrivate::get(&r->d->profiler);
+ profD->rhiDWhenEnabled = r->d;
+ }
+ r->d->debugMarkers = flags.testFlag(EnableDebugMarkers);
+ if (r->d->create(flags)) {
+ r->d->implType = impl;
+ r->d->implThread = QThread::currentThread();
+ return r.take();
+ }
+ }
+
+ return nullptr;
+}
+
+/*!
+ \return the backend type for this QRhi.
+ */
+QRhi::Implementation QRhi::backend() const
+{
+ return d->implType;
+}
+
+/*!
+ \return the thread on which the QRhi was \l{QRhi::create()}{initialized}.
+ */
+QThread *QRhi::thread() const
+{
+ return d->implThread;
+}
+
+/*!
+ Registers a \a callback that is invoked either when the QRhi is destroyed,
+ or when runCleanup() is called.
+
+ The callback will run with the graphics resource still available, so this
+ provides an opportunity for the application to cleanly release QRhiResource
+ instances belonging to the QRhi. This is particularly useful for managing
+ the lifetime of resources stored in \c cache type of objects, where the
+ cache holds QRhiResources or objects containing QRhiResources.
+
+ \sa runCleanup(), ~QRhi()
+ */
+void QRhi::addCleanupCallback(const CleanupCallback &callback)
+{
+ d->addCleanupCallback(callback);
+}
+
+/*!
+ Invokes all registered cleanup functions. The list of cleanup callbacks it
+ then cleared. Normally destroying the QRhi does this automatically, but
+ sometimes it can be useful to trigger cleanup in order to release all
+ cached, non-essential resources.
+
+ \sa addCleanupCallback()
+ */
+void QRhi::runCleanup()
+{
+ for (const CleanupCallback &f : qAsConst(d->cleanupCallbacks))
+ f(this);
+
+ d->cleanupCallbacks.clear();
+}
+
+/*!
+ \class QRhiResourceUpdateBatch
+ \inmodule QtRhi
+ \brief Records upload and copy type of operations.
+
+ With QRhi it is no longer possible to perform copy type of operations at
+ arbitrary times. Instead, all such operations are recorded into batches
+ that are then passed, most commonly, to QRhiCommandBuffer::beginPass().
+ What then happens under the hood is hidden from the application: the
+ underlying implementations can defer and implement these operations in
+ various different ways.
+
+ A resource update batch owns no graphics resources and does not perform any
+ actual operations on its own. It should rather be viewed as a command
+ buffer for update, upload, and copy type of commands.
+
+ To get an available, empty batch from the pool, call
+ QRhi::nextResourceUpdateBatch().
+ */
+
+/*!
+ \internal
+ */
+QRhiResourceUpdateBatch::QRhiResourceUpdateBatch(QRhiImplementation *rhi)
+ : d(new QRhiResourceUpdateBatchPrivate)
+{
+ d->q = this;
+ d->rhi = rhi;
+}
+
+QRhiResourceUpdateBatch::~QRhiResourceUpdateBatch()
+{
+ delete d;
+}
+
+/*!
+ \return the batch to the pool. This should only be used when the batch is
+ not passed to one of QRhiCommandBuffer::beginPass(),
+ QRhiCommandBuffer::endPass(), or QRhiCommandBuffer::resourceUpdate()
+ because these implicitly call release().
+
+ \note QRhiResourceUpdateBatch instances must never by \c deleted by
+ applications.
+ */
+void QRhiResourceUpdateBatch::release()
+{
+ d->free();
+}
+
+/*!
+ Copies all queued operations from the \a other batch into this one.
+
+ \note \a other is not changed in any way, typically it will still need a
+ release()
+
+ This allows for a convenient pattern where resource updates that are
+ already known during the initialization step are collected into a batch
+ that is then merged into another when starting to first render pass later
+ on:
+
+ \badcode
+ void init()
+ {
+ ...
+ initialUpdates = rhi->nextResourceUpdateBatch();
+ initialUpdates->uploadStaticBuffer(vbuf, vertexData);
+ initialUpdates->uploadStaticBuffer(ibuf, indexData);
+ ...
+ }
+
+ void render()
+ {
+ ...
+ QRhiResourceUpdateBatch *resUpdates = rhi->nextResourceUpdateBatch();
+ if (initialUpdates) {
+ resUpdates->merge(initialUpdates);
+ initialUpdates->release();
+ initialUpdates = nullptr;
+ }
+ resUpdates->updateDynamicBuffer(...);
+ ...
+ cb->beginPass(rt, clearCol, clearDs, resUpdates);
+ }
+ \endcode
+ */
+void QRhiResourceUpdateBatch::merge(QRhiResourceUpdateBatch *other)
+{
+ d->merge(other->d);
+}
+
+/*!
+ Enqueues updating a region of a QRhiBuffer \a buf created with the type
+ QRhiBuffer::Dynamic.
+
+ The region is specified \a offset and \a size. The actual bytes to write
+ are specified by \a data which must have at least \a size bytes available.
+ \a data can safely be destroyed or changed once this function returns.
+
+ \note If host writes are involved, which is the case with
+ updateDynamicBuffer() typically as such buffers are backed by host visible
+ memory with most backends, they may accumulate within a frame. Thus pass 1
+ reading a region changed by a batch passed to pass 2 may see the changes
+ specified in pass 2's update batch.
+
+ \note QRhi transparently manages double buffering in order to prevent
+ stalling the graphics pipeline. The fact that a QRhiBuffer may have
+ multiple native underneath can be safely ignored when using the QRhi and
+ QRhiResourceUpdateBatch.
+ */
+void QRhiResourceUpdateBatch::updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
+{
+ if (size > 0)
+ d->dynamicBufferUpdates.append({ buf, offset, size, data });
+}
+
+/*!
+ Enqueues updating a region of a QRhiBuffer \a buf created with the type
+ QRhiBuffer::Immutable or QRhiBuffer::Static.
+
+ The region is specified \a offset and \a size. The actual bytes to write
+ are specified by \a data which must have at least \a size bytes available.
+ \a data can safely be destroyed or changed once this function returns.
+ */
+void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data)
+{
+ if (size > 0)
+ d->staticBufferUploads.append({ buf, offset, size, data });
+}
+
+/*!
+ Enqueues updating the entire QRhiBuffer \a buf created with the type
+ QRhiBuffer::Immutable or QRhiBuffer::Static.
+ */
+void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, const void *data)
+{
+ if (buf->size() > 0)
+ d->staticBufferUploads.append({ buf, 0, 0, data });
+}
+
+/*!
+ Enqueues uploading the image data for one or more mip levels in one or more
+ layers of the texture \a tex.
+
+ The details of the copy (source QImage or compressed texture data, regions,
+ target layers and levels) are described in \a desc.
+ */
+void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
+{
+ if (!desc.entries().isEmpty())
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureUpload(tex, desc));
+}
+
+/*!
+ Enqueues uploading the image data for mip level 0 of layer 0 of the texture
+ \a tex.
+
+ \a tex must have an uncompressed format. Its format must also be compatible
+ with the QImage::format() of \a image. The source data is given in \a
+ image.
+ */
+void QRhiResourceUpdateBatch::uploadTexture(QRhiTexture *tex, const QImage &image)
+{
+ uploadTexture(tex, QRhiTextureUploadEntry(0, 0, image));
+}
+
+/*!
+ Enqueues a texture-to-texture copy operation from \a src into \a dst as
+ described by \a desc.
+
+ \note The source texture \a src must be created with
+ QRhiTexture::UsedAsTransferSource.
+ */
+void QRhiResourceUpdateBatch::copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureCopy(dst, src, desc));
+}
+
+/*!
+ Enqueues a texture-to-host copy operation as described by \a rb.
+
+ Normally \a rb will specify a QRhiTexture as the source. However, when the
+ swapchain in the current frame was created with
+ QRhiSwapChain::UsedAsTransferSource, it can also be the source of the
+ readback. For this, leave the texture set to null in \a rb.
+
+ Unlike other operations, the results here need to be processed by the
+ application. Therefore, \a result provides not just the data but also a
+ callback as operations on the batch are asynchronous by nature:
+
+ \badcode
+ beginFrame(sc);
+ beginPass
+ ...
+ QRhiReadbackResult *rbResult = new QRhiReadbackResult;
+ rbResult->completed = [rbResult] {
+ {
+ const QImage::Format fmt = QImage::Format_RGBA8888_Premultiplied; // fits QRhiTexture::RGBA8
+ const uchar *p = reinterpret_cast<const uchar *>(rbResult->data.constData());
+ QImage image(p, rbResult->pixelSize.width(), rbResult->pixelSize.height(), fmt);
+ image.save("result.png");
+ }
+ delete rbResult;
+ };
+ u = nextResourceUpdateBatch();
+ QRhiReadbackDescription rb; // no texture -> uses the current backbuffer of sc
+ u->readBackTexture(rb, rbResult);
+ endPass(u);
+ endFrame(sc);
+ \endcode
+
+ \note The texture must be created with QRhiTexture::UsedAsTransferSource.
+
+ \note Multisample textures cannot be read back.
+
+ \note The readback returns raw byte data, in order to allow the applications
+ to interpret it in any way they see fit. Be aware of the blending settings
+ of rendering code: if the blending is set up to rely on premultiplied alpha,
+ the results of the readback must also be interpreted as Premultiplied.
+
+ \note When interpreting the resulting raw data, be aware that the readback
+ happens with a byte ordered format. A \l{QRhiTexture::RGBA8}{RGBA8} texture
+ maps therefore to byte ordered QImage formats, such as,
+ QImage::Format_RGBA8888.
+ */
+void QRhiResourceUpdateBatch::readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureRead(rb, result));
+}
+
+/*!
+ Enqueues a mipmap generation operation for the specified \a layer of texture
+ \a tex.
+
+ \note The texture must be created with QRhiTexture::MipMapped and
+ QRhiTexture::UsedWithGenerateMips.
+ */
+void QRhiResourceUpdateBatch::generateMips(QRhiTexture *tex, int layer)
+{
+ d->textureOps.append(QRhiResourceUpdateBatchPrivate::TextureOp::textureMipGen(tex, layer));
+}
+
+/*!
+ \return an available, empty batch to which copy type of operations can be
+ recorded.
+
+ \note the return value is not owned by the caller and must never be
+ destroyed. Instead, the batch is returned the the pool for reuse by passing
+ it to QRhiCommandBuffer::beginPass(), QRhiCommandBuffer::endPass(), or
+ QRhiCommandBuffer::resourceUpdate(), or by calling
+ QRhiResourceUpdateBatch::release() on it.
+
+ \note Can be called outside beginFrame() - endFrame() as well since a batch
+ instance just collects data on its own, it does not perform any operations.
+ */
+QRhiResourceUpdateBatch *QRhi::nextResourceUpdateBatch()
+{
+ auto nextFreeBatch = [this]() -> QRhiResourceUpdateBatch * {
+ for (int i = 0, ie = d->resUpdPoolMap.count(); i != ie; ++i) {
+ if (!d->resUpdPoolMap.testBit(i)) {
+ d->resUpdPoolMap.setBit(i);
+ QRhiResourceUpdateBatch *u = d->resUpdPool[i];
+ QRhiResourceUpdateBatchPrivate::get(u)->poolIndex = i;
+ return u;
+ }
+ }
+ return nullptr;
+ };
+
+ QRhiResourceUpdateBatch *u = nextFreeBatch();
+ if (!u) {
+ const int oldSize = d->resUpdPool.count();
+ const int newSize = oldSize + 4;
+ d->resUpdPool.resize(newSize);
+ d->resUpdPoolMap.resize(newSize);
+ for (int i = oldSize; i < newSize; ++i)
+ d->resUpdPool[i] = new QRhiResourceUpdateBatch(d);
+ u = nextFreeBatch();
+ Q_ASSERT(u);
+ }
+
+ return u;
+}
+
+void QRhiResourceUpdateBatchPrivate::free()
+{
+ Q_ASSERT(poolIndex >= 0 && rhi->resUpdPool[poolIndex] == q);
+
+ dynamicBufferUpdates.clear();
+ staticBufferUploads.clear();
+ textureOps.clear();
+
+ rhi->resUpdPoolMap.clearBit(poolIndex);
+ poolIndex = -1;
+}
+
+void QRhiResourceUpdateBatchPrivate::merge(QRhiResourceUpdateBatchPrivate *other)
+{
+ dynamicBufferUpdates += other->dynamicBufferUpdates;
+ staticBufferUploads += other->staticBufferUploads;
+ textureOps += other->textureOps;
+}
+
+/*!
+ Sometimes committing resource updates is necessary without starting a
+ render pass. Not often needed, updates should typically be passed to
+ beginPass (or endPass, in case of readbacks) instead.
+
+ \note Cannot be called inside a pass.
+ */
+void QRhiCommandBuffer::resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ m_rhi->resourceUpdate(this, resourceUpdates);
+}
+
+/*!
+ Records starting a new render pass targeting the render target \a rt.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+
+ The color and depth/stencil buffers of the render target are normally
+ cleared. The clear values are specified in \a colorClearValue and \a
+ depthStencilClearValue. The exception is when the render target was created
+ with QRhiTextureRenderTarget::PreserveColorContents and/or
+ QRhiTextureRenderTarget::PreserveDepthStencilContents. The clear values are
+ ignored then.
+
+ \note Enabling preserved color or depth contents leads to decreased
+ performance depending on the underlying hardware. Mobile GPUs with tiled
+ architecture benefit from not having to reload the previous contents into
+ the tile buffer. Similarly, a QRhiTextureRenderTarget with a QRhiTexture as
+ the depth buffer is less efficient than a QRhiRenderBuffer since using a
+ depth texture triggers requiring writing the data out to it, while with
+ renderbuffers this is not needed (as the API does not allow sampling or
+ reading from a renderbuffer).
+
+ \note Do not assume that any state or resource bindings persist between
+ passes.
+
+ \note The QRhiCommandBuffer's \c set and \c draw functions can only be
+ called inside a pass. Also, with the exception of setGraphicsPipeline(),
+ they expect to have a pipeline set already on the command buffer.
+ Unspecified issues may arise otherwise, depending on the backend.
+ */
+void QRhiCommandBuffer::beginPass(QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->beginPass(this, rt, colorClearValue, depthStencilClearValue, resourceUpdates);
+}
+
+/*!
+ Records ending the current render pass.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+ */
+void QRhiCommandBuffer::endPass(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->endPass(this, resourceUpdates);
+}
+
+/*!
+ Records setting a new graphics pipeline \a ps.
+
+ \note This function must be called before recording other \c set or \c draw
+ commands on the command buffer.
+
+ \note QRhi will optimize out unnecessary invocations within a pass, so
+ therefore overoptimizing to avoid calls to this function is not necessary
+ on the applications' side.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::setGraphicsPipeline(QRhiGraphicsPipeline *ps)
+{
+ m_rhi->setGraphicsPipeline(this, ps);
+}
+
+/*!
+ Records binding a set of shader resources, such as, uniform buffers or
+ textures, that are made visible to one or more shader stages.
+
+ \a srb can be null in which case the current graphics or compute pipeline's
+ associated QRhiShaderResourceBindings is used. When \a srb is non-null, it
+ must be
+ \l{QRhiShaderResourceBindings::isLayoutCompatible()}{layout-compatible},
+ meaning the layout (number of bindings, the type and binding number of each
+ binding) must fully match the QRhiShaderResourceBindings that was
+ associated with the pipeline at the time of calling the pipeline's build().
+
+ There are cases when a seemingly unnecessary setShaderResources() call is
+ mandatory: when rebuilding a resource referenced from \a srb, for example
+ changing the size of a QRhiBuffer followed by a QRhiBuffer::build(), this
+ is the place where associated native objects (such as descriptor sets in
+ case of Vulkan) are updated to refer to the current native resources that
+ back the QRhiBuffer, QRhiTexture, QRhiSampler objects referenced from \a
+ srb. In this case setShaderResources() must be called even if \a srb is
+ the same as in the last call.
+
+ \a dynamicOffsets allows specifying buffer offsets for uniform buffers that
+ were associated with \a srb via
+ QRhiShaderResourceBinding::uniformBufferWithDynamicOffset(). This is
+ different from providing the offset in the \a srb itself: dynamic offsets
+ do not require building a new QRhiShaderResourceBindings for every
+ different offset, can avoid writing the underlying descriptors (with
+ backends where applicable), and so they may be more efficient. Each element
+ of \a dynamicOffsets is a \c binding - \c offset pair.
+ \a dynamicOffsetCount specifies the number of elements in \a dynamicOffsets.
+
+ \note All offsets in \a dynamicOffsets must be byte aligned to the value
+ returned from QRhi::ubufAlignment().
+
+ \note QRhi will optimize out unnecessary invocations within a pass (taking
+ the conditions described above into account), so therefore overoptimizing
+ to avoid calls to this function is not necessary on the applications' side.
+
+ \note This function can only be called inside a render or compute pass,
+ meaning between a beginPass() and endPass(), or beginComputePass() and
+ endComputePass().
+ */
+void QRhiCommandBuffer::setShaderResources(QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const DynamicOffset *dynamicOffsets)
+{
+ m_rhi->setShaderResources(this, srb, dynamicOffsetCount, dynamicOffsets);
+}
+
+/*!
+ Records vertex input bindings.
+
+ The index buffer used by subsequent drawIndexed() commands is specified by
+ \a indexBuf, \a indexOffset, and \a indexFormat. \a indexBuf can be set to
+ null when indexed drawing is not needed.
+
+ Vertex buffer bindings are batched. \a startBinding specifies the first
+ binding number. The recorded command then binds each buffer from \a
+ bindings to the binding point \c{startBinding + i} where \c i is the index
+ in \a bindings. Each element in \a bindings specifies a QRhiBuffer and an
+ offset.
+
+ Superfluous vertex input and index changes in the same pass are ignored
+ automatically with most backends and therefore applications do not need to
+ overoptimize to avoid calls to this function.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+
+ As a simple example, take a vertex shader with two inputs:
+
+ \badcode
+ layout(location = 0) in vec4 position;
+ layout(location = 1) in vec3 color;
+ \endcode
+
+ and assume we have the data available in interleaved format, using only 2
+ floats for position (so 5 floats per vertex: x, y, r, g, b). A QRhiGraphicsPipeline for
+ this shader can then be created using the input layout:
+
+ \badcode
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) }
+ });
+ \endcode
+
+ Here there is one buffer binding (binding number 0), with two inputs
+ referencing it. When recording the pass, once the pipeline is set, the
+ vertex bindings can be specified simply like the following (using C++11
+ initializer syntax), assuming vbuf is the QRhiBuffer with all the
+ interleaved position+color data:
+
+ \badcode
+ const QRhiCommandBuffer::VertexInput vbufBinding(vbuf, 0);
+ cb->setVertexInput(0, 1, &vbufBinding);
+ \endcode
+ */
+void QRhiCommandBuffer::setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ IndexFormat indexFormat)
+{
+ m_rhi->setVertexInput(this, startBinding, bindingCount, bindings, indexBuf, indexOffset, indexFormat);
+}
+
+/*!
+ Records setting the active viewport rectangle specified in \a viewport.
+
+ With backends where the underlying graphics API has scissoring always
+ enabled, this function also sets the scissor to match the viewport whenever
+ the active QRhiGraphicsPipeline does not have
+ \l{QRhiGraphicsPipeline::UsesScissor}{UsesScissor} set.
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::setViewport(const QRhiViewport &viewport)
+{
+ m_rhi->setViewport(this, viewport);
+}
+
+/*!
+ Records setting the active scissor rectangle specified in \a scissor.
+
+ This can only be called when the bound pipeline has
+ \l{QRhiGraphicsPipeline::UsesScissor}{UsesScissor} set. When the flag is
+ set on the active pipeline, this function must be called because scissor
+ testing will get enabled and so a scissor rectangle must be provided.
+
+ \note QRhi assumes OpenGL-style viewport coordinates, meaning x and y are
+ bottom-left.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::setScissor(const QRhiScissor &scissor)
+{
+ m_rhi->setScissor(this, scissor);
+}
+
+/*!
+ Records setting the active blend constants to \a c.
+
+ This can only be called when the bound pipeline has
+ QRhiGraphicsPipeline::UsesBlendConstants set.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::setBlendConstants(const QColor &c)
+{
+ m_rhi->setBlendConstants(this, c);
+}
+
+/*!
+ Records setting the active stencil reference value to \a refValue.
+
+ This can only be called when the bound pipeline has
+ QRhiGraphicsPipeline::UsesStencilRef set.
+
+ \note This function can only be called inside a render pass, meaning between
+ a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::setStencilRef(quint32 refValue)
+{
+ m_rhi->setStencilRef(this, refValue);
+}
+
+/*!
+ Records a non-indexed draw.
+
+ The number of vertices is specified in \a vertexCount. For instanced
+ drawing set \a instanceCount to a value other than 1. \a firstVertex is the
+ index of the first vertex to draw. When drawing multiple instances, the
+ first instance ID is specified by \a firstInstance.
+
+ \note \a firstInstance may not be supported, and is ignored when the
+ QRhi::BaseInstance feature is reported as not supported. The first ID is
+ always 0 in that case.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::draw(quint32 vertexCount,
+ quint32 instanceCount,
+ quint32 firstVertex,
+ quint32 firstInstance)
+{
+ m_rhi->draw(this, vertexCount, instanceCount, firstVertex, firstInstance);
+}
+
+/*!
+ Records an indexed draw.
+
+ The number of vertices is specified in \a indexCount. \a firstIndex is the
+ base index. The effective offset in the index buffer is given by
+ \c{indexOffset + firstIndex * n} where \c n is 2 or 4 depending on the
+ index element type. \c indexOffset is specified in setVertexInput().
+
+ \note The effective offset in the index buffer must be 4 byte aligned with
+ some backends (for example, Metal). With these backends the
+ \l{QRhi::NonFourAlignedEffectiveIndexBufferOffset}{NonFourAlignedEffectiveIndexBufferOffset}
+ feature will be reported as not-supported.
+
+ For instanced drawing set \a instanceCount to a value other than 1. When
+ drawing multiple instances, the first instance ID is specified by \a
+ firstInstance.
+
+ \note \a firstInstance may not be supported, and is ignored when the
+ QRhi::BaseInstance feature is reported as not supported. The first ID is
+ always 0 in that case.
+
+ \a vertexOffset (also called \c{base vertex}) is a signed value that is
+ added to the element index before indexing into the vertex buffer. Support
+ for this is not always available, and the value is ignored when the feature
+ QRhi::BaseVertex is reported as unsupported.
+
+ \note This function can only be called inside a render pass, meaning
+ between a beginPass() and endPass() call.
+ */
+void QRhiCommandBuffer::drawIndexed(quint32 indexCount,
+ quint32 instanceCount,
+ quint32 firstIndex,
+ qint32 vertexOffset,
+ quint32 firstInstance)
+{
+ m_rhi->drawIndexed(this, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance);
+}
+
+/*!
+ Records a named debug group on the command buffer. This is shown in
+ graphics debugging tools such as \l{https://renderdoc.org/}{RenderDoc} and
+ \l{https://developer.apple.com/xcode/}{XCode}. The end of the grouping is
+ indicated by debugMarkEnd().
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note Can be called anywhere within the frame, both inside and outside of passes.
+ */
+void QRhiCommandBuffer::debugMarkBegin(const QByteArray &name)
+{
+ m_rhi->debugMarkBegin(this, name);
+}
+
+/*!
+ Records the end of a debug group.
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note Can be called anywhere within the frame, both inside and outside of passes.
+ */
+void QRhiCommandBuffer::debugMarkEnd()
+{
+ m_rhi->debugMarkEnd(this);
+}
+
+/*!
+ Inserts a debug message \a msg into the command stream.
+
+ \note Ignored when QRhi::DebugMarkers are not supported or
+ QRhi::EnableDebugMarkers is not set.
+
+ \note With some backends debugMarkMsg() is only supported inside a pass and
+ is ignored when called outside a pass. With others it is recorded anywhere
+ within the frame.
+ */
+void QRhiCommandBuffer::debugMarkMsg(const QByteArray &msg)
+{
+ m_rhi->debugMarkMsg(this, msg);
+}
+
+/*!
+ Records starting a new compute pass.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+
+ \note Do not assume that any state or resource bindings persist between
+ passes.
+
+ \note A compute pass can record setComputePipeline(), setShaderResources(),
+ and dispatch() calls, not graphics ones. General functionality, such as,
+ debug markers and beginExternal() is available both in render and compute
+ passes.
+
+ \note Compute is only available when the \l{QRhi::Compute}{Compute} feature
+ is reported as supported.
+ */
+void QRhiCommandBuffer::beginComputePass(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->beginComputePass(this, resourceUpdates);
+}
+
+/*!
+ Records ending the current compute pass.
+
+ \a resourceUpdates, when not null, specifies a resource update batch that
+ is to be committed and then released.
+ */
+void QRhiCommandBuffer::endComputePass(QRhiResourceUpdateBatch *resourceUpdates)
+{
+ m_rhi->endComputePass(this, resourceUpdates);
+}
+
+/*!
+ Records setting a new compute pipeline \a ps.
+
+ \note This function must be called before recording setShaderResources() or
+ dispatch() commands on the command buffer.
+
+ \note QRhi will optimize out unnecessary invocations within a pass, so
+ therefore overoptimizing to avoid calls to this function is not necessary
+ on the applications' side.
+
+ \note This function can only be called inside a compute pass, meaning
+ between a beginComputePass() and endComputePass() call.
+ */
+void QRhiCommandBuffer::setComputePipeline(QRhiComputePipeline *ps)
+{
+ m_rhi->setComputePipeline(this, ps);
+}
+
+/*!
+ Records dispatching compute work items, with \a x, \a y, and \a z
+ specifying the number of local workgroups in the corresponding dimension.
+
+ \note This function can only be called inside a compute pass, meaning
+ between a beginComputePass() and endComputePass() call.
+ */
+void QRhiCommandBuffer::dispatch(int x, int y, int z)
+{
+ m_rhi->dispatch(this, x, y, z);
+}
+
+/*!
+ \return a pointer to a backend-specific QRhiNativeHandles subclass, such as
+ QRhiVulkanCommandBufferNativeHandles. The returned value is null when
+ exposing the underlying native resources is not supported by, or not
+ applicable to, the backend.
+
+ \sa QRhiVulkanCommandBufferNativeHandles,
+ QRhiMetalCommandBufferNativeHandles, beginExternal(), endExternal()
+ */
+const QRhiNativeHandles *QRhiCommandBuffer::nativeHandles()
+{
+ return m_rhi->nativeHandles(this);
+}
+
+/*!
+ To be called when the application before the application is about to
+ enqueue commands to the current pass' command buffer by calling graphics
+ API functions directly.
+
+ With Vulkan or Metal one can query the native command buffer or encoder
+ objects via nativeHandles() and enqueue commands to them. With OpenGL or
+ Direct3D 11 the (device) context can be retrieved from
+ QRhi::nativeHandles(). However, this must never be done without ensuring
+ the QRhiCommandBuffer's state stays up-to-date. Hence the requirement for
+ wrapping any externally added command recording between beginExternal() and
+ endExternal(). Conceptually this is the same as QPainter's
+ \l{QPainter::beginNativePainting()}{beginNativePainting()} and
+ \l{QPainter::endNativePainting()}{endNativePainting()} functions.
+
+ For OpenGL in particular, this function has an additional task: it makes
+ sure the context is made current on the current thread.
+
+ \note Once beginExternal() is called, no other render pass specific
+ functions (\c set* or \c draw*) must be called on the
+ QRhiCommandBuffer until endExternal().
+
+ \sa endExternal(), nativeHandles()
+ */
+void QRhiCommandBuffer::beginExternal()
+{
+ m_rhi->beginExternal(this);
+}
+
+/*!
+ To be called once the externally added commands are recorded to the command
+ buffer or context.
+
+ \note All QRhiCommandBuffer state must be assumed as invalid after calling
+ this function. Pipelines, vertex and index buffers, and other state must be
+ set again if more draw calls are recorded after the external commands.
+
+ \sa beginExternal(), nativeHandles()
+ */
+void QRhiCommandBuffer::endExternal()
+{
+ m_rhi->endExternal(this);
+}
+
+/*!
+ \return the value (typically an offset) \a v aligned to the uniform buffer
+ alignment given by by ubufAlignment().
+ */
+int QRhi::ubufAligned(int v) const
+{
+ const int byteAlign = ubufAlignment();
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+/*!
+ \return the number of mip levels for a given \a size.
+ */
+int QRhi::mipLevelsForSize(const QSize &size) const
+{
+ return qFloor(std::log2(qMax(size.width(), size.height()))) + 1;
+}
+
+/*!
+ \return the texture image size for a given \a mipLevel, calculated based on
+ the level 0 size given in \a baseLevelSize.
+ */
+QSize QRhi::sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const
+{
+ const int w = qMax(1, baseLevelSize.width() >> mipLevel);
+ const int h = qMax(1, baseLevelSize.height() >> mipLevel);
+ return QSize(w, h);
+}
+
+/*!
+ \return \c true if the underlying graphics API has the Y axis pointing up
+ in framebuffers and images.
+
+ In practice this is \c true for OpenGL only.
+ */
+bool QRhi::isYUpInFramebuffer() const
+{
+ return d->isYUpInFramebuffer();
+}
+
+/*!
+ \return \c true if the underlying graphics API has the Y axis pointing up
+ in its normalized device coordinate system.
+
+ In practice this is \c false for Vulkan only.
+
+ \note clipSpaceCorrMatrix() includes the corresponding adjustment (to make
+ Y point up) in its returned matrix.
+ */
+bool QRhi::isYUpInNDC() const
+{
+ return d->isYUpInNDC();
+}
+
+/*!
+ \return \c true if the underlying graphics API uses depth 0 - 1 in clip
+ space.
+
+ In practice this is \c false for OpenGL only.
+
+ \note clipSpaceCorrMatrix() includes the corresponding adjustment in its
+ returned matrix.
+ */
+bool QRhi::isClipDepthZeroToOne() const
+{
+ return d->isClipDepthZeroToOne();
+}
+
+/*!
+ \return a matrix that can be used to allow applications keep using
+ OpenGL-targeted vertex data and perspective projection matrices (such as,
+ the ones generated by QMatrix4x4::perspective()), regardless of the
+ backend. Once \c{this_matrix * mvp} is used instead of just \c mvp, vertex
+ data with Y up and viewports with depth range 0 - 1 can be used without
+ considering what backend and so graphics API is going to be used at run
+ time.
+
+ See
+ \l{https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/}{this
+ page} for a discussion of the topic from Vulkan perspective.
+ */
+QMatrix4x4 QRhi::clipSpaceCorrMatrix() const
+{
+ return d->clipSpaceCorrMatrix();
+}
+
+/*!
+ \return \c true if the specified texture \a format modified by \a flags is
+ supported.
+
+ The query is supported both for uncompressed and compressed formats.
+ */
+bool QRhi::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ return d->isTextureFormatSupported(format, flags);
+}
+
+/*!
+ \return \c true if the specified \a feature is supported
+ */
+bool QRhi::isFeatureSupported(QRhi::Feature feature) const
+{
+ return d->isFeatureSupported(feature);
+}
+
+/*!
+ \return the value for the specified resource \a limit.
+
+ The values are expected to be queried by the backends upon initialization,
+ meaning calling this function is a light operation.
+ */
+int QRhi::resourceLimit(ResourceLimit limit) const
+{
+ return d->resourceLimit(limit);
+}
+
+/*!
+ \return a pointer to the backend-specific collection of native objects
+ for the device, context, and similar concepts used by the backend.
+
+ Cast to QRhiVulkanNativeHandles, QRhiD3D11NativeHandles,
+ QRhiGles2NativeHandles, QRhiMetalNativeHandles as appropriate.
+
+ \note No ownership is transferred, neither for the returned pointer nor for
+ any native objects.
+ */
+const QRhiNativeHandles *QRhi::nativeHandles()
+{
+ return d->nativeHandles();
+}
+
+/*!
+ With OpenGL this makes the OpenGL context current on the current thread.
+ The function has no effect with other backends.
+
+ Calling this function is relevant typically in Qt framework code, when one
+ has to ensure external OpenGL code provided by the application can still
+ run like it did before with direct usage of OpenGL, as long as the QRhi is
+ using the OpenGL backend.
+ */
+void QRhi::makeThreadLocalNativeContextCurrent()
+{
+ d->makeThreadLocalNativeContextCurrent();
+}
+
+/*!
+ \return the associated QRhiProfiler instance.
+
+ An instance is always available for each QRhi, but it is not very useful
+ without EnableProfiling because no data is collected without setting the
+ flag upon creation.
+ */
+QRhiProfiler *QRhi::profiler()
+{
+ return &d->profiler;
+}
+
+/*!
+ \return a new graphics pipeline resource.
+
+ \sa QRhiResource::release()
+ */
+QRhiGraphicsPipeline *QRhi::newGraphicsPipeline()
+{
+ return d->createGraphicsPipeline();
+}
+
+/*!
+ \return a new compute pipeline resource.
+
+ \note Compute is only available when the \l{QRhi::Compute}{Compute} feature
+ is reported as supported.
+
+ \sa QRhiResource::release()
+ */
+QRhiComputePipeline *QRhi::newComputePipeline()
+{
+ return d->createComputePipeline();
+}
+
+/*!
+ \return a new shader resource binding collection resource.
+
+ \sa QRhiResource::release()
+ */
+QRhiShaderResourceBindings *QRhi::newShaderResourceBindings()
+{
+ return d->createShaderResourceBindings();
+}
+
+/*!
+ \return a new buffer with the specified \a type, \a usage, and \a size.
+
+ \note Some \a usage and \a type combinations may not be supported by all
+ backends. See \l{QRhiBuffer::UsageFlag}{UsageFlags} and
+ \l{QRhi::NonDynamicUniformBuffers}{the feature flags}.
+
+ \sa QRhiResource::release()
+ */
+QRhiBuffer *QRhi::newBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size)
+{
+ return d->createBuffer(type, usage, size);
+}
+
+/*!
+ \return a new renderbuffer with the specified \a type, \a pixelSize, \a
+ sampleCount, and \a flags.
+
+ \sa QRhiResource::release()
+ */
+QRhiRenderBuffer *QRhi::newRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags)
+{
+ return d->createRenderBuffer(type, pixelSize, sampleCount, flags);
+}
+
+/*!
+ \return a new texture with the specified \a format, \a pixelSize, \a
+ sampleCount, and \a flags.
+
+ \note \a format specifies the requested internal and external format,
+ meaning the data to be uploaded to the texture will need to be in a
+ compatible format, while the native texture may (but is not guaranteed to,
+ in case of OpenGL at least) use this format internally.
+
+ \sa QRhiResource::release()
+ */
+QRhiTexture *QRhi::newTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags)
+{
+ return d->createTexture(format, pixelSize, sampleCount, flags);
+}
+
+/*!
+ \return a new sampler with the specified magnification filter \a magFilter,
+ minification filter \a minFilter, mipmapping mode \a mipmapMpde, and S/T
+ addressing modes \a u and \a v.
+
+ \sa QRhiResource::release()
+ */
+QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v)
+{
+ return d->createSampler(magFilter, minFilter, mipmapMode, u, v);
+}
+
+/*!
+ \return a new texture render target with color and depth/stencil
+ attachments given in \a desc, and with the specified \a flags.
+
+ \sa QRhiResource::release()
+ */
+
+QRhiTextureRenderTarget *QRhi::newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return d->createTextureRenderTarget(desc, flags);
+}
+
+/*!
+ \return a new swapchain.
+
+ \sa QRhiResource::release(), QRhiSwapChain::buildOrResize()
+ */
+QRhiSwapChain *QRhi::newSwapChain()
+{
+ return d->createSwapChain();
+}
+
+/*!
+ Starts a new frame targeting the next available buffer of \a swapChain.
+
+ The high level pattern of rendering into a QWindow using a swapchain:
+
+ \list
+
+ \li Create a swapchain.
+
+ \li Call QRhiSwapChain::buildOrResize() whenever the surface size is
+ different than before.
+
+ \li Call QRhiSwapChain::release() on
+ QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed.
+
+ \li Then on every frame:
+ \badcode
+ beginFrame(sc);
+ updates = nextResourceUpdateBatch();
+ updates->...
+ QRhiCommandBuffer *cb = sc->currentFrameCommandBuffer();
+ cb->beginPass(sc->currentFrameRenderTarget(), colorClear, dsClear, updates);
+ ...
+ cb->endPass();
+ ... // more passes as necessary
+ endFrame(sc);
+ \endcode
+
+ \endlist
+
+ \a flags is currently unused.
+
+ \sa endFrame()
+ */
+QRhi::FrameOpResult QRhi::beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags)
+{
+ if (d->inFrame)
+ qWarning("Attempted to call beginFrame() within a still active frame; ignored");
+
+ QRhi::FrameOpResult r = !d->inFrame ? d->beginFrame(swapChain, flags) : FrameOpSuccess;
+ if (r == FrameOpSuccess)
+ d->inFrame = true;
+
+ return r;
+}
+
+/*!
+ Ends, commits, and presents a frame that was started in the last
+ beginFrame() on \a swapChain.
+
+ Double (or triple) buffering is managed internally by the QRhiSwapChain and
+ QRhi.
+
+ \a flags can optionally be used to change the behavior in certain ways.
+ Passing QRhi::SkipPresent skips queuing the Present command or calling
+ swapBuffers.
+
+ \sa beginFrame()
+ */
+QRhi::FrameOpResult QRhi::endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags)
+{
+ if (!d->inFrame)
+ qWarning("Attempted to call endFrame() without an active frame; ignored");
+
+ QRhi::FrameOpResult r = d->inFrame ? d->endFrame(swapChain, flags) : FrameOpSuccess;
+ d->inFrame = false;
+ // releaseAndDestroyLater is a high level QRhi concept the backends know
+ // nothing about - handle it here.
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ return r;
+}
+
+/*!
+ \return true when there is an active frame, meaning there was a
+ beginFrame() (or beginOffscreenFrame()) with no corresponding endFrame()
+ (or endOffscreenFrame()) yet.
+
+ \sa currentFrameSlot(), beginFrame(), endFrame()
+ */
+bool QRhi::isRecordingFrame() const
+{
+ return d->inFrame;
+}
+
+/*!
+ \return the current frame slot index while recording a frame. Unspecified
+ when called outside an active frame (that is, when isRecordingFrame() is \c
+ false).
+
+ With backends like Vulkan or Metal, it is the responsibility of the QRhi
+ backend to block whenever starting a new frame and finding the CPU is
+ already \c{FramesInFlight - 1} frames ahead of the GPU (because the command
+ buffer submitted in frame no. \c{current} - \c{FramesInFlight} has not yet
+ completed).
+
+ Resources that tend to change between frames (such as, the native buffer
+ object backing a QRhiBuffer with type QRhiBuffer::Dynamic) exist in
+ multiple versions, so that each frame, that can be submitted while a
+ previous one is still being processed, works with its own copy, thus
+ avoiding the need to stall the pipeline when preparing the frame. (The
+ contents of a resource that may still be in use in the GPU should not be
+ touched, but simply always waiting for the previous frame to finish would
+ reduce GPU utilization and ultimately, performance and efficiency.)
+
+ Conceptually this is somewhat similar to copy-on-write schemes used by some
+ C++ containers and other types. It may also be similar to what an OpenGL or
+ Direct 3D 11 implementation performs internally for certain type of objects.
+
+ In practice, such double (or tripple) buffering resources is realized in
+ the Vulkan, Metal, and similar QRhi backends by having a fixed number of
+ native resource (such as, VkBuffer) \c slots behind a QRhiResource. That
+ can then be indexed by a frame slot index running 0, 1, ..,
+ FramesInFlight-1, and then wrapping around.
+
+ All this is managed transparently to the users of QRhi. However,
+ applications that integrate rendering done directly with the graphics API
+ may want to perform a similar double or tripple buffering of their own
+ graphics resources. That is then most easily achieved by knowing the values
+ of the maximum number of in-flight frames (retrievable via resourceLimit())
+ and the current frame (slot) index (returned by this function).
+
+ \sa isRecordingFrame(), beginFrame(), endFrame()
+ */
+int QRhi::currentFrameSlot() const
+{
+ return d->currentFrameSlot;
+}
+
+/*!
+ Starts a new offscreen frame. Provides a command buffer suitable for
+ recording rendering commands in \a cb.
+
+ \note The QRhiCommandBuffer stored to *cb is not owned by the caller.
+
+ Rendering without a swapchain is possible as well. The typical use case is
+ to use it in completely offscreen applications, e.g. to generate image
+ sequences by rendering and reading back without ever showing a window.
+
+ Usage in on-screen applications (so beginFrame, endFrame,
+ beginOffscreenFrame, endOffscreenFrame, beginFrame, ...) is possible too
+ but it does reduce parallelism so it should be done only infrequently.
+
+ Offscreen frames do not let the CPU - potentially - generate another frame
+ while the GPU is still processing the previous one. This has the side
+ effect that if readbacks are scheduled, the results are guaranteed to be
+ available once endOffscreenFrame() returns. That is not the case with
+ frames targeting a swapchain.
+
+ The skeleton of rendering a frame without a swapchain and then reading the
+ frame contents back could look like the following:
+
+ \badcode
+ QRhiReadbackResult rbResult;
+ QRhiCommandBuffer *cb;
+ beginOffscreenFrame(&cb);
+ beginPass
+ ...
+ u = nextResourceUpdateBatch();
+ u->readBackTexture(rb, &rbResult);
+ endPass(u);
+ endOffscreenFrame();
+ // image data available in rbResult
+ \endcode
+
+ \sa endOffscreenFrame()
+ */
+QRhi::FrameOpResult QRhi::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ if (d->inFrame)
+ qWarning("Attempted to call beginOffscreenFrame() within a still active frame; ignored");
+
+ QRhi::FrameOpResult r = !d->inFrame ? d->beginOffscreenFrame(cb) : FrameOpSuccess;
+ if (r == FrameOpSuccess)
+ d->inFrame = true;
+
+ return r;
+}
+
+/*!
+ Ends and waits for the offscreen frame.
+
+ \sa beginOffscreenFrame()
+ */
+QRhi::FrameOpResult QRhi::endOffscreenFrame()
+{
+ if (!d->inFrame)
+ qWarning("Attempted to call endOffscreenFrame() without an active frame; ignored");
+
+ QRhi::FrameOpResult r = d->inFrame ? d->endOffscreenFrame() : FrameOpSuccess;
+ d->inFrame = false;
+ qDeleteAll(d->pendingReleaseAndDestroyResources);
+ d->pendingReleaseAndDestroyResources.clear();
+
+ return r;
+}
+
+/*!
+ Waits for any work on the graphics queue (where applicable) to complete,
+ then executes all deferred operations, like completing readbacks and
+ resource releases. Can be called inside and outside of a frame, but not
+ inside a pass. Inside a frame it implies submitting any work on the
+ command buffer.
+
+ \note Avoid this function. One case where it may be needed is when the
+ results of an enqueued readback in a swapchain-based frame are needed at a
+ fixed given point and so waiting for the results is desired.
+ */
+QRhi::FrameOpResult QRhi::finish()
+{
+ return d->finish();
+}
+
+/*!
+ \return the list of supported sample counts.
+
+ A typical example would be (1, 2, 4, 8).
+
+ With some backend this list of supported values is fixed in advance, while
+ with some others the (physical) device properties indicate what is
+ supported at run time.
+ */
+QVector<int> QRhi::supportedSampleCounts() const
+{
+ return d->supportedSampleCounts();
+}
+
+/*!
+ \return the minimum uniform buffer offset alignment in bytes. This is
+ typically 256.
+
+ Attempting to bind a uniform buffer region with an offset not aligned to
+ this value will lead to failures depending on the backend and the
+ underlying graphics API.
+
+ \sa ubufAligned()
+ */
+int QRhi::ubufAlignment() const
+{
+ return d->ubufAlignment();
+}
+
+static QBasicAtomicInteger<QRhiGlobalObjectIdGenerator::Type> counter = Q_BASIC_ATOMIC_INITIALIZER(0);
+
+QRhiGlobalObjectIdGenerator::Type QRhiGlobalObjectIdGenerator::newId()
+{
+ return counter.fetchAndAddRelaxed(1) + 1;
+}
+
+bool QRhiPassResourceTracker::isEmpty() const
+{
+ return m_buffers.isEmpty() && m_textures.isEmpty();
+}
+
+void QRhiPassResourceTracker::reset()
+{
+ m_buffers.clear();
+ m_textures.clear();
+}
+
+static inline QRhiPassResourceTracker::BufferStage earlierStage(QRhiPassResourceTracker::BufferStage a,
+ QRhiPassResourceTracker::BufferStage b)
+{
+ return QRhiPassResourceTracker::BufferStage(qMin(int(a), int(b)));
+}
+
+void QRhiPassResourceTracker::registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage,
+ const UsageState &state)
+{
+ auto it = std::find_if(m_buffers.begin(), m_buffers.end(), [buf](const Buffer &b) { return b.buf == buf; });
+ if (it != m_buffers.end()) {
+ if (it->access != *access) {
+ const QByteArray name = buf->name();
+ qWarning("Buffer %p (%s) used with different accesses within the same pass, this is not allowed.",
+ buf, name.constData());
+ return;
+ }
+ if (it->stage != *stage) {
+ it->stage = earlierStage(it->stage, *stage);
+ *stage = it->stage;
+ }
+ return;
+ }
+
+ Buffer b;
+ b.buf = buf;
+ b.slot = slot;
+ b.access = *access;
+ b.stage = *stage;
+ b.stateAtPassBegin = state; // first use -> initial state
+ m_buffers.append(b);
+}
+
+static inline QRhiPassResourceTracker::TextureStage earlierStage(QRhiPassResourceTracker::TextureStage a,
+ QRhiPassResourceTracker::TextureStage b)
+{
+ return QRhiPassResourceTracker::TextureStage(qMin(int(a), int(b)));
+}
+
+static inline bool isImageLoadStore(QRhiPassResourceTracker::TextureAccess access)
+{
+ return access == QRhiPassResourceTracker::TexStorageLoad
+ || access == QRhiPassResourceTracker::TexStorageStore
+ || access == QRhiPassResourceTracker::TexStorageLoadStore;
+}
+
+void QRhiPassResourceTracker::registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage,
+ const UsageState &state)
+{
+ auto it = std::find_if(m_textures.begin(), m_textures.end(), [tex](const Texture &t) { return t.tex == tex; });
+ if (it != m_textures.end()) {
+ if (it->access != *access) {
+ // Different subresources of a texture may be used for both load
+ // and store in the same pass. (think reading from one mip level
+ // and writing to another one in a compute shader) This we can
+ // handle by treating the entire resource as read-write.
+ if (isImageLoadStore(it->access) && isImageLoadStore(*access)) {
+ it->access = QRhiPassResourceTracker::TexStorageLoadStore;
+ *access = it->access;
+ } else {
+ const QByteArray name = tex->name();
+ qWarning("Texture %p (%s) used with different accesses within the same pass, this is not allowed.",
+ tex, name.constData());
+ }
+ }
+ if (it->stage != *stage) {
+ it->stage = earlierStage(it->stage, *stage);
+ *stage = it->stage;
+ }
+ return;
+ }
+
+ Texture t;
+ t.tex = tex;
+ t.access = *access;
+ t.stage = *stage;
+ t.stateAtPassBegin = state; // first use -> initial state
+ m_textures.append(t);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
new file mode 100644
index 0000000000..df30817ef4
--- /dev/null
+++ b/src/gui/rhi/qrhi_p.h
@@ -0,0 +1,1434 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHI_H
+#define QRHI_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <QSize>
+#include <QMatrix4x4>
+#include <QVector>
+#include <QThread>
+#include <QColor>
+#include <QImage>
+#include <functional>
+#include <array>
+#include <private/qshader_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindow;
+class QRhiImplementation;
+class QRhiBuffer;
+class QRhiRenderBuffer;
+class QRhiTexture;
+class QRhiSampler;
+class QRhiCommandBuffer;
+class QRhiResourceUpdateBatch;
+class QRhiResourceUpdateBatchPrivate;
+class QRhiProfiler;
+class QRhiShaderResourceBindingPrivate;
+
+class Q_GUI_EXPORT QRhiDepthStencilClearValue
+{
+public:
+ QRhiDepthStencilClearValue() = default;
+ QRhiDepthStencilClearValue(float d, quint32 s);
+
+ float depthClearValue() const { return m_d; }
+ void setDepthClearValue(float d) { m_d = d; }
+
+ quint32 stencilClearValue() const { return m_s; }
+ void setStencilClearValue(quint32 s) { m_s = s; }
+
+private:
+ float m_d = 1.0f;
+ quint32 m_s = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiDepthStencilClearValue, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiDepthStencilClearValue &a, const QRhiDepthStencilClearValue &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiDepthStencilClearValue &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiDepthStencilClearValue &);
+#endif
+
+class Q_GUI_EXPORT QRhiViewport
+{
+public:
+ QRhiViewport() = default;
+ QRhiViewport(float x, float y, float w, float h, float minDepth = 0.0f, float maxDepth = 1.0f);
+
+ std::array<float, 4> viewport() const { return m_rect; }
+ void setViewport(float x, float y, float w, float h) {
+ m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h;
+ }
+
+ float minDepth() const { return m_minDepth; }
+ void setMinDepth(float minDepth) { m_minDepth = minDepth; }
+
+ float maxDepth() const { return m_maxDepth; }
+ void setMaxDepth(float maxDepth) { m_maxDepth = maxDepth; }
+
+private:
+ std::array<float, 4> m_rect { { 0.0f, 0.0f, 0.0f, 0.0f } };
+ float m_minDepth = 0.0f;
+ float m_maxDepth = 1.0f;
+};
+
+Q_DECLARE_TYPEINFO(QRhiViewport, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiViewport &a, const QRhiViewport &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiViewport &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiViewport &);
+#endif
+
+class Q_GUI_EXPORT QRhiScissor
+{
+public:
+ QRhiScissor() = default;
+ QRhiScissor(int x, int y, int w, int h);
+
+ std::array<int, 4> scissor() const { return m_rect; }
+ void setScissor(int x, int y, int w, int h) {
+ m_rect[0] = x; m_rect[1] = y; m_rect[2] = w; m_rect[3] = h;
+ }
+
+private:
+ std::array<int, 4> m_rect { { 0, 0, 0, 0 } };
+};
+
+Q_DECLARE_TYPEINFO(QRhiScissor, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiScissor &a, const QRhiScissor &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiScissor &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiScissor &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputBinding
+{
+public:
+ enum Classification {
+ PerVertex,
+ PerInstance
+ };
+
+ QRhiVertexInputBinding() = default;
+ QRhiVertexInputBinding(quint32 stride, Classification cls = PerVertex, int stepRate = 1);
+
+ quint32 stride() const { return m_stride; }
+ void setStride(quint32 s) { m_stride = s; }
+
+ Classification classification() const { return m_classification; }
+ void setClassification(Classification c) { m_classification = c; }
+
+ int instanceStepRate() const { return m_instanceStepRate; }
+ void setInstanceStepRate(int rate) { m_instanceStepRate = rate; }
+
+private:
+ quint32 m_stride = 0;
+ Classification m_classification = PerVertex;
+ int m_instanceStepRate = 1;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputBinding, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputBinding &a, const QRhiVertexInputBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputBinding &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputBinding &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputAttribute
+{
+public:
+ enum Format {
+ Float4,
+ Float3,
+ Float2,
+ Float,
+ UNormByte4,
+ UNormByte2,
+ UNormByte
+ };
+
+ QRhiVertexInputAttribute() = default;
+ QRhiVertexInputAttribute(int binding, int location, Format format, quint32 offset);
+
+ int binding() const { return m_binding; }
+ void setBinding(int b) { m_binding = b; }
+
+ int location() const { return m_location; }
+ void setLocation(int loc) { m_location = loc; }
+
+ Format format() const { return m_format; }
+ void setFormt(Format f) { m_format = f; }
+
+ quint32 offset() const { return m_offset; }
+ void setOffset(quint32 ofs) { m_offset = ofs; }
+
+private:
+ int m_binding = 0;
+ int m_location = 0;
+ Format m_format = Float4;
+ quint32 m_offset = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputAttribute, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputAttribute &a, const QRhiVertexInputAttribute &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputAttribute &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputAttribute &);
+#endif
+
+class Q_GUI_EXPORT QRhiVertexInputLayout
+{
+public:
+ QRhiVertexInputLayout() = default;
+
+ QVector<QRhiVertexInputBinding> bindings() const { return m_bindings; }
+ void setBindings(const QVector<QRhiVertexInputBinding> &v) { m_bindings = v; }
+
+ QVector<QRhiVertexInputAttribute> attributes() const { return m_attributes; }
+ void setAttributes(const QVector<QRhiVertexInputAttribute> &v) { m_attributes = v; }
+
+private:
+ QVector<QRhiVertexInputBinding> m_bindings;
+ QVector<QRhiVertexInputAttribute> m_attributes;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVertexInputLayout, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiVertexInputLayout &a, const QRhiVertexInputLayout &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiVertexInputLayout &v, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiVertexInputLayout &);
+#endif
+
+class Q_GUI_EXPORT QRhiShaderStage
+{
+public:
+ enum Type {
+ Vertex,
+ Fragment,
+ Compute
+ };
+
+ QRhiShaderStage() = default;
+ QRhiShaderStage(Type type, const QShader &shader,
+ QShader::Variant v = QShader::StandardShader);
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ QShader shader() const { return m_shader; }
+ void setShader(const QShader &s) { m_shader = s; }
+
+ QShader::Variant shaderVariant() const { return m_shaderVariant; }
+ void setShaderVariant(QShader::Variant v) { m_shaderVariant = v; }
+
+private:
+ Type m_type = Vertex;
+ QShader m_shader;
+ QShader::Variant m_shaderVariant = QShader::StandardShader;
+};
+
+Q_DECLARE_TYPEINFO(QRhiShaderStage, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiShaderStage &a, const QRhiShaderStage &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiShaderStage &s, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderStage &);
+#endif
+
+using QRhiGraphicsShaderStage = QRhiShaderStage;
+
+class Q_GUI_EXPORT QRhiShaderResourceBinding
+{
+public:
+ enum Type {
+ UniformBuffer,
+ SampledTexture,
+ ImageLoad,
+ ImageStore,
+ ImageLoadStore,
+ BufferLoad,
+ BufferStore,
+ BufferLoadStore
+ };
+
+ enum StageFlag {
+ VertexStage = 1 << 0,
+ FragmentStage = 1 << 1,
+ ComputeStage = 1 << 2
+ };
+ Q_DECLARE_FLAGS(StageFlags, StageFlag)
+
+ QRhiShaderResourceBinding();
+ QRhiShaderResourceBinding(const QRhiShaderResourceBinding &other);
+ QRhiShaderResourceBinding &operator=(const QRhiShaderResourceBinding &other);
+ ~QRhiShaderResourceBinding();
+ void detach();
+
+ bool isLayoutCompatible(const QRhiShaderResourceBinding &other) const;
+
+ static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf);
+ static QRhiShaderResourceBinding uniformBuffer(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size);
+ static QRhiShaderResourceBinding uniformBufferWithDynamicOffset(int binding, StageFlags stage, QRhiBuffer *buf, int size);
+
+ static QRhiShaderResourceBinding sampledTexture(int binding, StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler);
+
+ static QRhiShaderResourceBinding imageLoad(int binding, StageFlags stage, QRhiTexture *tex, int level);
+ static QRhiShaderResourceBinding imageStore(int binding, StageFlags stage, QRhiTexture *tex, int level);
+ static QRhiShaderResourceBinding imageLoadStore(int binding, StageFlags stage, QRhiTexture *tex, int level);
+
+ static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf);
+ static QRhiShaderResourceBinding bufferLoad(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size);
+ static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf);
+ static QRhiShaderResourceBinding bufferStore(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size);
+ static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf);
+ static QRhiShaderResourceBinding bufferLoadStore(int binding, StageFlags stage, QRhiBuffer *buf, int offset, int size);
+
+private:
+ QRhiShaderResourceBindingPrivate *d;
+ friend class QRhiShaderResourceBindingPrivate;
+ friend Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &, const QRhiShaderResourceBinding &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT uint qHash(const QRhiShaderResourceBinding &, uint) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
+#endif
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBinding::StageFlags)
+
+Q_GUI_EXPORT bool operator==(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator!=(const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QRhiShaderResourceBinding &b, uint seed = 0) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBinding &);
+#endif
+
+class Q_GUI_EXPORT QRhiColorAttachment
+{
+public:
+ QRhiColorAttachment() = default;
+ QRhiColorAttachment(QRhiTexture *texture);
+ QRhiColorAttachment(QRhiRenderBuffer *renderBuffer);
+
+ QRhiTexture *texture() const { return m_texture; }
+ void setTexture(QRhiTexture *tex) { m_texture = tex; }
+
+ QRhiRenderBuffer *renderBuffer() const { return m_renderBuffer; }
+ void setRenderBuffer(QRhiRenderBuffer *rb) { m_renderBuffer = rb; }
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+ QRhiTexture *resolveTexture() const { return m_resolveTexture; }
+ void setResolveTexture(QRhiTexture *tex) { m_resolveTexture = tex; }
+
+ int resolveLayer() const { return m_resolveLayer; }
+ void setResolveLayer(int layer) { m_resolveLayer = layer; }
+
+ int resolveLevel() const { return m_resolveLevel; }
+ void setResolveLevel(int level) { m_resolveLevel = level; }
+
+private:
+ QRhiTexture *m_texture = nullptr;
+ QRhiRenderBuffer *m_renderBuffer = nullptr;
+ int m_layer = 0;
+ int m_level = 0;
+ QRhiTexture *m_resolveTexture = nullptr;
+ int m_resolveLayer = 0;
+ int m_resolveLevel = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureRenderTargetDescription
+{
+public:
+ QRhiTextureRenderTargetDescription() = default;
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment);
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiRenderBuffer *depthStencilBuffer);
+ QRhiTextureRenderTargetDescription(const QRhiColorAttachment &colorAttachment, QRhiTexture *depthTexture);
+
+ QVector<QRhiColorAttachment> colorAttachments() const { return m_colorAttachments; }
+ void setColorAttachments(const QVector<QRhiColorAttachment> &att) { m_colorAttachments = att; }
+
+ QRhiRenderBuffer *depthStencilBuffer() const { return m_depthStencilBuffer; }
+ void setDepthStencilBuffer(QRhiRenderBuffer *renderBuffer) { m_depthStencilBuffer = renderBuffer; }
+
+ QRhiTexture *depthTexture() const { return m_depthTexture; }
+ void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; }
+
+private:
+ QVector<QRhiColorAttachment> m_colorAttachments;
+ QRhiRenderBuffer *m_depthStencilBuffer = nullptr;
+ QRhiTexture *m_depthTexture = nullptr;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureRenderTargetDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription
+{
+public:
+ QRhiTextureSubresourceUploadDescription() = default;
+ QRhiTextureSubresourceUploadDescription(const QImage &image);
+ QRhiTextureSubresourceUploadDescription(const void *data, int size);
+
+ QImage image() const { return m_image; }
+ void setImage(const QImage &image) { m_image = image; }
+
+ QByteArray data() const { return m_data; }
+ void setData(const QByteArray &data) { m_data = data; }
+
+ QPoint destinationTopLeft() const { return m_destinationTopLeft; }
+ void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; }
+
+ QSize sourceSize() const { return m_sourceSize; }
+ void setSourceSize(const QSize &size) { m_sourceSize = size; }
+
+ QPoint sourceTopLeft() const { return m_sourceTopLeft; }
+ void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; }
+
+private:
+ QImage m_image;
+ QByteArray m_data;
+ QPoint m_destinationTopLeft;
+ QSize m_sourceSize;
+ QPoint m_sourceTopLeft;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureSubresourceUploadDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureUploadEntry
+{
+public:
+ QRhiTextureUploadEntry() = default;
+ QRhiTextureUploadEntry(int layer, int level, const QRhiTextureSubresourceUploadDescription &desc);
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+ QRhiTextureSubresourceUploadDescription description() const { return m_desc; }
+ void setDescription(const QRhiTextureSubresourceUploadDescription &desc) { m_desc = desc; }
+
+private:
+ int m_layer = 0;
+ int m_level = 0;
+ QRhiTextureSubresourceUploadDescription m_desc;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureUploadEntry, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureUploadDescription
+{
+public:
+ QRhiTextureUploadDescription() = default;
+ QRhiTextureUploadDescription(const QRhiTextureUploadEntry &entry);
+ QRhiTextureUploadDescription(const QVector<QRhiTextureUploadEntry> &entries);
+
+ QVector<QRhiTextureUploadEntry> entries() const { return m_entries; }
+ void setEntries(const QVector<QRhiTextureUploadEntry> &entries) { m_entries = entries; }
+ void append(const QRhiTextureUploadEntry &entry);
+
+private:
+ QVector<QRhiTextureUploadEntry> m_entries;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureUploadDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiTextureCopyDescription
+{
+public:
+ QRhiTextureCopyDescription() = default;
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ int sourceLayer() const { return m_sourceLayer; }
+ void setSourceLayer(int layer) { m_sourceLayer = layer; }
+
+ int sourceLevel() const { return m_sourceLevel; }
+ void setSourceLevel(int level) { m_sourceLevel = level; }
+
+ QPoint sourceTopLeft() const { return m_sourceTopLeft; }
+ void setSourceTopLeft(const QPoint &p) { m_sourceTopLeft = p; }
+
+ int destinationLayer() const { return m_destinationLayer; }
+ void setDestinationLayer(int layer) { m_destinationLayer = layer; }
+
+ int destinationLevel() const { return m_destinationLevel; }
+ void setDestinationLevel(int level) { m_destinationLevel = level; }
+
+ QPoint destinationTopLeft() const { return m_destinationTopLeft; }
+ void setDestinationTopLeft(const QPoint &p) { m_destinationTopLeft = p; }
+
+private:
+ QSize m_pixelSize;
+ int m_sourceLayer = 0;
+ int m_sourceLevel = 0;
+ QPoint m_sourceTopLeft;
+ int m_destinationLayer = 0;
+ int m_destinationLevel = 0;
+ QPoint m_destinationTopLeft;
+};
+
+Q_DECLARE_TYPEINFO(QRhiTextureCopyDescription, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiReadbackDescription
+{
+public:
+ QRhiReadbackDescription() = default;
+ QRhiReadbackDescription(QRhiTexture *texture);
+
+ QRhiTexture *texture() const { return m_texture; }
+ void setTexture(QRhiTexture *tex) { m_texture = tex; }
+
+ int layer() const { return m_layer; }
+ void setLayer(int layer) { m_layer = layer; }
+
+ int level() const { return m_level; }
+ void setLevel(int level) { m_level = level; }
+
+private:
+ QRhiTexture *m_texture = nullptr;
+ int m_layer = 0;
+ int m_level = 0;
+};
+
+Q_DECLARE_TYPEINFO(QRhiReadbackDescription, Q_MOVABLE_TYPE);
+
+struct Q_GUI_EXPORT QRhiNativeHandles
+{
+};
+
+class Q_GUI_EXPORT QRhiResource
+{
+public:
+ enum Type {
+ Buffer,
+ Texture,
+ Sampler,
+ RenderBuffer,
+ RenderPassDescriptor,
+ RenderTarget,
+ TextureRenderTarget,
+ ShaderResourceBindings,
+ GraphicsPipeline,
+ SwapChain,
+ ComputePipeline,
+ CommandBuffer
+ };
+
+ virtual ~QRhiResource();
+
+ virtual Type resourceType() const = 0;
+
+ virtual void release() = 0;
+ void releaseAndDestroyLater();
+
+ QByteArray name() const;
+ void setName(const QByteArray &name);
+
+ quint64 globalResourceId() const;
+
+protected:
+ QRhiResource(QRhiImplementation *rhi);
+ Q_DISABLE_COPY(QRhiResource)
+ friend class QRhiImplementation;
+ QRhiImplementation *m_rhi = nullptr;
+ quint64 m_id;
+ QByteArray m_objectName;
+};
+
+class Q_GUI_EXPORT QRhiBuffer : public QRhiResource
+{
+public:
+ enum Type {
+ Immutable,
+ Static,
+ Dynamic
+ };
+
+ enum UsageFlag {
+ VertexBuffer = 1 << 0,
+ IndexBuffer = 1 << 1,
+ UniformBuffer = 1 << 2,
+ StorageBuffer = 1 << 3
+ };
+ Q_DECLARE_FLAGS(UsageFlags, UsageFlag)
+
+ QRhiResource::Type resourceType() const override;
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ UsageFlags usage() const { return m_usage; }
+ void setUsage(UsageFlags u) { m_usage = u; }
+
+ int size() const { return m_size; }
+ void setSize(int sz) { m_size = sz; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiBuffer(QRhiImplementation *rhi, Type type_, UsageFlags usage_, int size_);
+ Type m_type;
+ UsageFlags m_usage;
+ int m_size;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiBuffer::UsageFlags)
+
+class Q_GUI_EXPORT QRhiTexture : public QRhiResource
+{
+public:
+ enum Flag {
+ RenderTarget = 1 << 0,
+ CubeMap = 1 << 2,
+ MipMapped = 1 << 3,
+ sRGB = 1 << 4,
+ UsedAsTransferSource = 1 << 5,
+ UsedWithGenerateMips = 1 << 6,
+ UsedWithLoadStore = 1 << 7
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum Format {
+ UnknownFormat,
+
+ RGBA8,
+ BGRA8,
+ R8,
+ R16,
+ RED_OR_ALPHA8,
+
+ RGBA16F,
+ RGBA32F,
+
+ D16,
+ D32F,
+
+ BC1,
+ BC2,
+ BC3,
+ BC4,
+ BC5,
+ BC6H,
+ BC7,
+
+ ETC2_RGB8,
+ ETC2_RGB8A1,
+ ETC2_RGBA8,
+
+ ASTC_4x4,
+ ASTC_5x4,
+ ASTC_5x5,
+ ASTC_6x5,
+ ASTC_6x6,
+ ASTC_8x5,
+ ASTC_8x6,
+ ASTC_8x8,
+ ASTC_10x5,
+ ASTC_10x6,
+ ASTC_10x8,
+ ASTC_10x10,
+ ASTC_12x10,
+ ASTC_12x12
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Format format() const { return m_format; }
+ void setFormat(Format fmt) { m_format = fmt; }
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ virtual bool build() = 0;
+ virtual const QRhiNativeHandles *nativeHandles();
+ virtual bool buildFrom(const QRhiNativeHandles *src);
+
+protected:
+ QRhiTexture(QRhiImplementation *rhi, Format format_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_);
+ Format m_format;
+ QSize m_pixelSize;
+ int m_sampleCount;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTexture::Flags)
+
+class Q_GUI_EXPORT QRhiSampler : public QRhiResource
+{
+public:
+ enum Filter {
+ None,
+ Nearest,
+ Linear
+ };
+
+ enum AddressMode {
+ Repeat,
+ ClampToEdge,
+ Border,
+ Mirror,
+ MirrorOnce
+ };
+
+ enum CompareOp {
+ Never,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Filter magFilter() const { return m_magFilter; }
+ void setMagFilter(Filter f) { m_magFilter = f; }
+
+ Filter minFilter() const { return m_minFilter; }
+ void setMinFilter(Filter f) { m_minFilter = f; }
+
+ Filter mipmapMode() const { return m_mipmapMode; }
+ void setMipmapMode(Filter f) { m_mipmapMode = f; }
+
+ AddressMode addressU() const { return m_addressU; }
+ void setAddressU(AddressMode mode) { m_addressU = mode; }
+
+ AddressMode addressV() const { return m_addressV; }
+ void setAddressV(AddressMode mode) { m_addressV = mode; }
+
+ AddressMode addressW() const { return m_addressW; }
+ void setAddressW(AddressMode mode) { m_addressW = mode; }
+
+ CompareOp textureCompareOp() const { return m_compareOp; }
+ void setTextureCompareOp(CompareOp op) { m_compareOp = op; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiSampler(QRhiImplementation *rhi,
+ Filter magFilter_, Filter minFilter_, Filter mipmapMode_,
+ AddressMode u_, AddressMode v_);
+ Filter m_magFilter;
+ Filter m_minFilter;
+ Filter m_mipmapMode;
+ AddressMode m_addressU;
+ AddressMode m_addressV;
+ AddressMode m_addressW;
+ CompareOp m_compareOp;
+};
+
+class Q_GUI_EXPORT QRhiRenderBuffer : public QRhiResource
+{
+public:
+ enum Type {
+ DepthStencil,
+ Color
+ };
+
+ enum Flag {
+ UsedWithSwapChainOnly = 1 << 0
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ Type type() const { return m_type; }
+ void setType(Type t) { m_type = t; }
+
+ QSize pixelSize() const { return m_pixelSize; }
+ void setPixelSize(const QSize &sz) { m_pixelSize = sz; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags h) { m_flags = h; }
+
+ virtual bool build() = 0;
+
+ virtual QRhiTexture::Format backingFormat() const = 0;
+
+protected:
+ QRhiRenderBuffer(QRhiImplementation *rhi, Type type_, const QSize &pixelSize_,
+ int sampleCount_, Flags flags_);
+ Type m_type;
+ QSize m_pixelSize;
+ int m_sampleCount;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiRenderBuffer::Flags)
+
+class Q_GUI_EXPORT QRhiRenderPassDescriptor : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+protected:
+ QRhiRenderPassDescriptor(QRhiImplementation *rhi);
+};
+
+class Q_GUI_EXPORT QRhiRenderTarget : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+ virtual QSize pixelSize() const = 0;
+ virtual float devicePixelRatio() const = 0;
+ virtual int sampleCount() const = 0;
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+protected:
+ QRhiRenderTarget(QRhiImplementation *rhi);
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+};
+
+class Q_GUI_EXPORT QRhiTextureRenderTarget : public QRhiRenderTarget
+{
+public:
+ enum Flag {
+ PreserveColorContents = 1 << 0,
+ PreserveDepthStencilContents = 1 << 1
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ QRhiTextureRenderTargetDescription description() const { return m_desc; }
+ void setDescription(const QRhiTextureRenderTargetDescription &desc) { m_desc = desc; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0;
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc_, Flags flags_);
+ QRhiTextureRenderTargetDescription m_desc;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTextureRenderTarget::Flags)
+
+class Q_GUI_EXPORT QRhiShaderResourceBindings : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+
+ QVector<QRhiShaderResourceBinding> bindings() const { return m_bindings; }
+ void setBindings(const QVector<QRhiShaderResourceBinding> &b) { m_bindings = b; }
+
+ bool isLayoutCompatible(const QRhiShaderResourceBindings *other) const;
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiShaderResourceBindings(QRhiImplementation *rhi);
+ QVector<QRhiShaderResourceBinding> m_bindings;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &);
+#endif
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &);
+#endif
+
+class Q_GUI_EXPORT QRhiGraphicsPipeline : public QRhiResource
+{
+public:
+ enum Flag {
+ UsesBlendConstants = 1 << 0,
+ UsesStencilRef = 1 << 1,
+ UsesScissor = 1 << 2
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum Topology {
+ Triangles,
+ TriangleStrip,
+ Lines,
+ LineStrip,
+ Points
+ };
+
+ enum CullMode {
+ None,
+ Front,
+ Back
+ };
+
+ enum FrontFace {
+ CCW,
+ CW
+ };
+
+ enum ColorMaskComponent {
+ R = 1 << 0,
+ G = 1 << 1,
+ B = 1 << 2,
+ A = 1 << 3
+ };
+ Q_DECLARE_FLAGS(ColorMask, ColorMaskComponent)
+
+ enum BlendFactor {
+ Zero,
+ One,
+ SrcColor,
+ OneMinusSrcColor,
+ DstColor,
+ OneMinusDstColor,
+ SrcAlpha,
+ OneMinusSrcAlpha,
+ DstAlpha,
+ OneMinusDstAlpha,
+ ConstantColor,
+ OneMinusConstantColor,
+ ConstantAlpha,
+ OneMinusConstantAlpha,
+ SrcAlphaSaturate,
+ Src1Color,
+ OneMinusSrc1Color,
+ Src1Alpha,
+ OneMinusSrc1Alpha
+ };
+
+ enum BlendOp {
+ Add,
+ Subtract,
+ ReverseSubtract,
+ Min,
+ Max
+ };
+
+ struct TargetBlend {
+ ColorMask colorWrite = ColorMask(0xF); // R | G | B | A
+ bool enable = false;
+ BlendFactor srcColor = One;
+ BlendFactor dstColor = OneMinusSrcAlpha;
+ BlendOp opColor = Add;
+ BlendFactor srcAlpha = One;
+ BlendFactor dstAlpha = OneMinusSrcAlpha;
+ BlendOp opAlpha = Add;
+ };
+
+ enum CompareOp {
+ Never,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always
+ };
+
+ enum StencilOp {
+ StencilZero,
+ Keep,
+ Replace,
+ IncrementAndClamp,
+ DecrementAndClamp,
+ Invert,
+ IncrementAndWrap,
+ DecrementAndWrap
+ };
+
+ struct StencilOpState {
+ StencilOp failOp = Keep;
+ StencilOp depthFailOp = Keep;
+ StencilOp passOp = Keep;
+ CompareOp compareOp = Always;
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ Topology topology() const { return m_topology; }
+ void setTopology(Topology t) { m_topology = t; }
+
+ CullMode cullMode() const { return m_cullMode; }
+ void setCullMode(CullMode mode) { m_cullMode = mode; }
+
+ FrontFace frontFace() const { return m_frontFace; }
+ void setFrontFace(FrontFace f) { m_frontFace = f; }
+
+ QVector<TargetBlend> targetBlends() const { return m_targetBlends; }
+ void setTargetBlends(const QVector<TargetBlend> &blends) { m_targetBlends = blends; }
+
+ bool hasDepthTest() const { return m_depthTest; }
+ void setDepthTest(bool enable) { m_depthTest = enable; }
+
+ bool hasDepthWrite() const { return m_depthWrite; }
+ void setDepthWrite(bool enable) { m_depthWrite = enable; }
+
+ CompareOp depthOp() const { return m_depthOp; }
+ void setDepthOp(CompareOp op) { m_depthOp = op; }
+
+ bool hasStencilTest() const { return m_stencilTest; }
+ void setStencilTest(bool enable) { m_stencilTest = enable; }
+
+ StencilOpState stencilFront() const { return m_stencilFront; }
+ void setStencilFront(const StencilOpState &state) { m_stencilFront = state; }
+
+ StencilOpState stencilBack() const { return m_stencilBack; }
+ void setStencilBack(const StencilOpState &state) { m_stencilBack = state; }
+
+ quint32 stencilReadMask() const { return m_stencilReadMask; }
+ void setStencilReadMask(quint32 mask) { m_stencilReadMask = mask; }
+
+ quint32 stencilWriteMask() const { return m_stencilWriteMask; }
+ void setStencilWriteMask(quint32 mask) { m_stencilWriteMask = mask; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int s) { m_sampleCount = s; }
+
+ float lineWidth() const { return m_lineWidth; }
+ void setLineWidth(float width) { m_lineWidth = width; }
+
+ QVector<QRhiShaderStage> shaderStages() const { return m_shaderStages; }
+ void setShaderStages(const QVector<QRhiShaderStage> &stages) { m_shaderStages = stages; }
+
+ QRhiVertexInputLayout vertexInputLayout() const { return m_vertexInputLayout; }
+ void setVertexInputLayout(const QRhiVertexInputLayout &layout) { m_vertexInputLayout = layout; }
+
+ QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; }
+ void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; }
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+ virtual bool build() = 0;
+
+protected:
+ QRhiGraphicsPipeline(QRhiImplementation *rhi);
+ Flags m_flags;
+ Topology m_topology = Triangles;
+ CullMode m_cullMode = None;
+ FrontFace m_frontFace = CCW;
+ QVector<TargetBlend> m_targetBlends;
+ bool m_depthTest = false;
+ bool m_depthWrite = false;
+ CompareOp m_depthOp = Less;
+ bool m_stencilTest = false;
+ StencilOpState m_stencilFront;
+ StencilOpState m_stencilBack;
+ quint32 m_stencilReadMask = 0xFF;
+ quint32 m_stencilWriteMask = 0xFF;
+ int m_sampleCount = 1;
+ float m_lineWidth = 1.0f;
+ QVector<QRhiShaderStage> m_shaderStages;
+ QRhiVertexInputLayout m_vertexInputLayout;
+ QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr;
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::Flags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiGraphicsPipeline::ColorMask)
+Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiSwapChain : public QRhiResource
+{
+public:
+ enum Flag {
+ SurfaceHasPreMulAlpha = 1 << 0,
+ SurfaceHasNonPreMulAlpha = 1 << 1,
+ sRGB = 1 << 2,
+ UsedAsTransferSource = 1 << 3,
+ NoVSync = 1 << 4,
+ MinimalBufferCount = 1 << 5
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QRhiResource::Type resourceType() const override;
+
+ QWindow *window() const { return m_window; }
+ void setWindow(QWindow *window) { m_window = window; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+ QRhiRenderBuffer *depthStencil() const { return m_depthStencil; }
+ void setDepthStencil(QRhiRenderBuffer *ds) { m_depthStencil = ds; }
+
+ int sampleCount() const { return m_sampleCount; }
+ void setSampleCount(int samples) { m_sampleCount = samples; }
+
+ QRhiRenderPassDescriptor *renderPassDescriptor() const { return m_renderPassDesc; }
+ void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc) { m_renderPassDesc = desc; }
+
+ QSize currentPixelSize() const { return m_currentPixelSize; }
+
+ virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0;
+ virtual QRhiRenderTarget *currentFrameRenderTarget() = 0;
+ virtual QSize surfacePixelSize() = 0;
+ virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0;
+ virtual bool buildOrResize() = 0;
+
+protected:
+ QRhiSwapChain(QRhiImplementation *rhi);
+ QWindow *m_window = nullptr;
+ Flags m_flags;
+ QRhiRenderBuffer *m_depthStencil = nullptr;
+ int m_sampleCount = 1;
+ QRhiRenderPassDescriptor *m_renderPassDesc = nullptr;
+ QSize m_currentPixelSize;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiSwapChain::Flags)
+
+class Q_GUI_EXPORT QRhiComputePipeline : public QRhiResource
+{
+public:
+ QRhiResource::Type resourceType() const override;
+ virtual bool build() = 0;
+
+ QRhiShaderStage shaderStage() const { return m_shaderStage; }
+ void setShaderStage(const QRhiShaderStage &stage) { m_shaderStage = stage; }
+
+ QRhiShaderResourceBindings *shaderResourceBindings() const { return m_shaderResourceBindings; }
+ void setShaderResourceBindings(QRhiShaderResourceBindings *srb) { m_shaderResourceBindings = srb; }
+
+protected:
+ QRhiComputePipeline(QRhiImplementation *rhi);
+ QRhiShaderStage m_shaderStage;
+ QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr;
+};
+
+class Q_GUI_EXPORT QRhiCommandBuffer : public QRhiResource
+{
+public:
+ enum IndexFormat {
+ IndexUInt16,
+ IndexUInt32
+ };
+
+ QRhiResource::Type resourceType() const override;
+
+ void resourceUpdate(QRhiResourceUpdateBatch *resourceUpdates);
+
+ void beginPass(QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+ void endPass(QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+
+ void setGraphicsPipeline(QRhiGraphicsPipeline *ps);
+ using DynamicOffset = QPair<int, quint32>; // binding, offset
+ void setShaderResources(QRhiShaderResourceBindings *srb = nullptr,
+ int dynamicOffsetCount = 0,
+ const DynamicOffset *dynamicOffsets = nullptr);
+ using VertexInput = QPair<QRhiBuffer *, quint32>; // buffer, offset
+ void setVertexInput(int startBinding, int bindingCount, const VertexInput *bindings,
+ QRhiBuffer *indexBuf = nullptr, quint32 indexOffset = 0,
+ IndexFormat indexFormat = IndexUInt16);
+
+ void setViewport(const QRhiViewport &viewport);
+ void setScissor(const QRhiScissor &scissor);
+ void setBlendConstants(const QColor &c);
+ void setStencilRef(quint32 refValue);
+
+ void draw(quint32 vertexCount,
+ quint32 instanceCount = 1,
+ quint32 firstVertex = 0,
+ quint32 firstInstance = 0);
+
+ void drawIndexed(quint32 indexCount,
+ quint32 instanceCount = 1,
+ quint32 firstIndex = 0,
+ qint32 vertexOffset = 0,
+ quint32 firstInstance = 0);
+
+ void debugMarkBegin(const QByteArray &name);
+ void debugMarkEnd();
+ void debugMarkMsg(const QByteArray &msg);
+
+ void beginComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+ void endComputePass(QRhiResourceUpdateBatch *resourceUpdates = nullptr);
+ void setComputePipeline(QRhiComputePipeline *ps);
+ void dispatch(int x, int y, int z);
+
+ const QRhiNativeHandles *nativeHandles();
+ void beginExternal();
+ void endExternal();
+
+protected:
+ QRhiCommandBuffer(QRhiImplementation *rhi);
+};
+
+struct Q_GUI_EXPORT QRhiReadbackResult
+{
+ std::function<void()> completed = nullptr;
+ QRhiTexture::Format format;
+ QSize pixelSize;
+ QByteArray data;
+}; // non-movable due to the std::function
+
+class Q_GUI_EXPORT QRhiResourceUpdateBatch
+{
+public:
+ ~QRhiResourceUpdateBatch();
+
+ void release();
+
+ void merge(QRhiResourceUpdateBatch *other);
+
+ void updateDynamicBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
+ void uploadStaticBuffer(QRhiBuffer *buf, int offset, int size, const void *data);
+ void uploadStaticBuffer(QRhiBuffer *buf, const void *data);
+ void uploadTexture(QRhiTexture *tex, const QRhiTextureUploadDescription &desc);
+ void uploadTexture(QRhiTexture *tex, const QImage &image);
+ void copyTexture(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc = QRhiTextureCopyDescription());
+ void readBackTexture(const QRhiReadbackDescription &rb, QRhiReadbackResult *result);
+ void generateMips(QRhiTexture *tex, int layer = 0);
+
+private:
+ QRhiResourceUpdateBatch(QRhiImplementation *rhi);
+ Q_DISABLE_COPY(QRhiResourceUpdateBatch)
+ QRhiResourceUpdateBatchPrivate *d;
+ friend class QRhiResourceUpdateBatchPrivate;
+ friend class QRhi;
+};
+
+struct Q_GUI_EXPORT QRhiInitParams
+{
+};
+
+class Q_GUI_EXPORT QRhi
+{
+public:
+ enum Implementation {
+ Null,
+ Vulkan,
+ OpenGLES2,
+ D3D11,
+ Metal
+ };
+
+ enum Flag {
+ EnableProfiling = 1 << 0,
+ EnableDebugMarkers = 1 << 1
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ enum FrameOpResult {
+ FrameOpSuccess = 0,
+ FrameOpError,
+ FrameOpSwapChainOutOfDate,
+ FrameOpDeviceLost
+ };
+
+ enum Feature {
+ MultisampleTexture = 1,
+ MultisampleRenderBuffer,
+ DebugMarkers,
+ Timestamps,
+ Instancing,
+ CustomInstanceStepRate,
+ PrimitiveRestart,
+ NonDynamicUniformBuffers,
+ NonFourAlignedEffectiveIndexBufferOffset,
+ NPOTTextureRepeat,
+ RedOrAlpha8IsRed,
+ ElementIndexUint,
+ Compute,
+ WideLines,
+ VertexShaderPointSize,
+ BaseVertex,
+ BaseInstance
+ };
+
+ enum BeginFrameFlag {
+ };
+ Q_DECLARE_FLAGS(BeginFrameFlags, BeginFrameFlag)
+
+ enum EndFrameFlag {
+ SkipPresent = 1 << 0
+ };
+ Q_DECLARE_FLAGS(EndFrameFlags, EndFrameFlag)
+
+ enum ResourceLimit {
+ TextureSizeMin = 1,
+ TextureSizeMax,
+ MaxColorAttachments,
+ FramesInFlight
+ };
+
+ ~QRhi();
+
+ static QRhi *create(Implementation impl,
+ QRhiInitParams *params,
+ Flags flags = Flags(),
+ QRhiNativeHandles *importDevice = nullptr);
+
+ Implementation backend() const;
+ QThread *thread() const;
+
+ using CleanupCallback = std::function<void(QRhi *)>;
+ void addCleanupCallback(const CleanupCallback &callback);
+ void runCleanup();
+
+ QRhiGraphicsPipeline *newGraphicsPipeline();
+ QRhiComputePipeline *newComputePipeline();
+ QRhiShaderResourceBindings *newShaderResourceBindings();
+
+ QRhiBuffer *newBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size);
+
+ QRhiRenderBuffer *newRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount = 1,
+ QRhiRenderBuffer::Flags flags = QRhiRenderBuffer::Flags());
+
+ QRhiTexture *newTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount = 1,
+ QRhiTexture::Flags flags = QRhiTexture::Flags());
+
+ QRhiSampler *newSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v);
+
+ QRhiTextureRenderTarget *newTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags = QRhiTextureRenderTarget::Flags());
+
+ QRhiSwapChain *newSwapChain();
+ FrameOpResult beginFrame(QRhiSwapChain *swapChain, BeginFrameFlags flags = BeginFrameFlags());
+ FrameOpResult endFrame(QRhiSwapChain *swapChain, EndFrameFlags flags = EndFrameFlags());
+ bool isRecordingFrame() const;
+ int currentFrameSlot() const;
+
+ FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb);
+ FrameOpResult endOffscreenFrame();
+
+ QRhi::FrameOpResult finish();
+
+ QRhiResourceUpdateBatch *nextResourceUpdateBatch();
+
+ QVector<int> supportedSampleCounts() const;
+
+ int ubufAlignment() const;
+ int ubufAligned(int v) const;
+
+ int mipLevelsForSize(const QSize &size) const;
+ QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) const;
+
+ bool isYUpInFramebuffer() const;
+ bool isYUpInNDC() const;
+ bool isClipDepthZeroToOne() const;
+
+ QMatrix4x4 clipSpaceCorrMatrix() const;
+
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags = QRhiTexture::Flags()) const;
+ bool isFeatureSupported(QRhi::Feature feature) const;
+ int resourceLimit(ResourceLimit limit) const;
+
+ const QRhiNativeHandles *nativeHandles();
+ void makeThreadLocalNativeContextCurrent();
+
+ QRhiProfiler *profiler();
+
+ static const int MAX_LAYERS = 6; // cubemaps only
+ static const int MAX_LEVELS = 16; // a width and/or height of 65536 should be enough for everyone
+
+protected:
+ QRhi();
+
+private:
+ Q_DISABLE_COPY(QRhi)
+ QRhiImplementation *d = nullptr;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::Flags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::BeginFrameFlags)
+Q_DECLARE_OPERATORS_FOR_FLAGS(QRhi::EndFrameFlags)
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhi_p_p.h b/src/gui/rhi/qrhi_p_p.h
new file mode 100644
index 0000000000..83d521f441
--- /dev/null
+++ b/src/gui/rhi/qrhi_p_p.h
@@ -0,0 +1,566 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHI_P_H
+#define QRHI_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhi_p.h"
+#include "qrhiprofiler_p_p.h"
+#include <QBitArray>
+#include <QAtomicInt>
+
+QT_BEGIN_NAMESPACE
+
+#define QRHI_RES(t, x) static_cast<t *>(x)
+#define QRHI_RES_RHI(t) t *rhiD = static_cast<t *>(m_rhi)
+#define QRHI_PROF QRhiProfilerPrivate *rhiP = m_rhi->profilerPrivateOrNull()
+#define QRHI_PROF_F(f) for (bool qrhip_enabled = rhiP != nullptr; qrhip_enabled; qrhip_enabled = false) rhiP->f
+
+class QRhiImplementation
+{
+public:
+ virtual ~QRhiImplementation();
+
+ virtual bool create(QRhi::Flags flags) = 0;
+ virtual void destroy() = 0;
+
+ virtual QRhiGraphicsPipeline *createGraphicsPipeline() = 0;
+ virtual QRhiComputePipeline *createComputePipeline() = 0;
+ virtual QRhiShaderResourceBindings *createShaderResourceBindings() = 0;
+ virtual QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) = 0;
+ virtual QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) = 0;
+ virtual QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) = 0;
+ virtual QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) = 0;
+
+ virtual QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) = 0;
+
+ virtual QRhiSwapChain *createSwapChain() = 0;
+ virtual QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) = 0;
+ virtual QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) = 0;
+ virtual QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) = 0;
+ virtual QRhi::FrameOpResult endOffscreenFrame() = 0;
+ virtual QRhi::FrameOpResult finish() = 0;
+
+ virtual void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+
+ virtual void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) = 0;
+ virtual void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+
+ virtual void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) = 0;
+
+ virtual void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) = 0;
+
+ virtual void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) = 0;
+
+ virtual void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) = 0;
+ virtual void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) = 0;
+ virtual void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) = 0;
+ virtual void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) = 0;
+
+ virtual void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) = 0;
+ virtual void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) = 0;
+
+ virtual void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) = 0;
+ virtual void debugMarkEnd(QRhiCommandBuffer *cb) = 0;
+ virtual void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) = 0;
+
+ virtual void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+ virtual void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) = 0;
+ virtual void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) = 0;
+ virtual void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) = 0;
+
+ virtual const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) = 0;
+ virtual void beginExternal(QRhiCommandBuffer *cb) = 0;
+ virtual void endExternal(QRhiCommandBuffer *cb) = 0;
+
+ virtual QVector<int> supportedSampleCounts() const = 0;
+ virtual int ubufAlignment() const = 0;
+ virtual bool isYUpInFramebuffer() const = 0;
+ virtual bool isYUpInNDC() const = 0;
+ virtual bool isClipDepthZeroToOne() const = 0;
+ virtual QMatrix4x4 clipSpaceCorrMatrix() const = 0;
+ virtual bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const = 0;
+ virtual bool isFeatureSupported(QRhi::Feature feature) const = 0;
+ virtual int resourceLimit(QRhi::ResourceLimit limit) const = 0;
+ virtual const QRhiNativeHandles *nativeHandles() = 0;
+ virtual void sendVMemStatsToProfiler() = 0;
+ virtual void makeThreadLocalNativeContextCurrent() = 0;
+
+ bool isCompressedFormat(QRhiTexture::Format format) const;
+ void compressedFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize,
+ QSize *blockDim) const;
+ void textureFormatInfo(QRhiTexture::Format format, const QSize &size,
+ quint32 *bpl, quint32 *byteSize) const;
+ quint32 approxByteSizeForTexture(QRhiTexture::Format format, const QSize &baseSize,
+ int mipCount, int layerCount);
+
+ QRhiProfilerPrivate *profilerPrivateOrNull()
+ {
+ // return null when QRhi::EnableProfiling was not set
+ QRhiProfilerPrivate *p = QRhiProfilerPrivate::get(&profiler);
+ return p->rhiDWhenEnabled ? p : nullptr;
+ }
+
+ // only really care about resources that own native graphics resources underneath
+ void registerResource(QRhiResource *res)
+ {
+ resources.insert(res);
+ }
+
+ void unregisterResource(QRhiResource *res)
+ {
+ resources.remove(res);
+ }
+
+ QSet<QRhiResource *> activeResources() const
+ {
+ return resources;
+ }
+
+ void addReleaseAndDestroyLater(QRhiResource *res)
+ {
+ if (inFrame)
+ pendingReleaseAndDestroyResources.insert(res);
+ else
+ delete res;
+ }
+
+ void addCleanupCallback(const QRhi::CleanupCallback &callback)
+ {
+ cleanupCallbacks.append(callback);
+ }
+
+ QRhi *q;
+
+protected:
+ bool debugMarkers = false;
+ int currentFrameSlot = 0; // for vk, mtl, and similar. unused by gl and d3d11.
+ bool inFrame = false;
+
+private:
+ QRhi::Implementation implType;
+ QThread *implThread;
+ QRhiProfiler profiler;
+ QVector<QRhiResourceUpdateBatch *> resUpdPool;
+ QBitArray resUpdPoolMap;
+ QSet<QRhiResource *> resources;
+ QSet<QRhiResource *> pendingReleaseAndDestroyResources;
+ QVector<QRhi::CleanupCallback> cleanupCallbacks;
+
+ friend class QRhi;
+ friend class QRhiResourceUpdateBatchPrivate;
+};
+
+template<typename T, size_t N>
+bool qrhi_toTopLeftRenderTargetRect(const QSize &outputSize, const std::array<T, N> &r,
+ T *x, T *y, T *w, T *h)
+{
+ // x,y are bottom-left in QRhiScissor and QRhiViewport but top-left in
+ // Vulkan/Metal/D3D. We also need proper clamping since some
+ // validation/debug layers are allergic to out of bounds scissor or
+ // viewport rects.
+
+ const T outputWidth = outputSize.width();
+ const T outputHeight = outputSize.height();
+ const T inputWidth = r[2];
+ const T inputHeight = r[3];
+
+ *x = qMax<T>(0, r[0]);
+ *y = qMax<T>(0, outputHeight - (r[1] + inputHeight));
+ *w = inputWidth;
+ *h = inputHeight;
+
+ if (*x >= outputWidth || *y >= outputHeight)
+ return false;
+
+ if (*x + *w > outputWidth)
+ *w = outputWidth - *x;
+ if (*y + *h > outputHeight)
+ *h = outputHeight - *y;
+
+ return true;
+}
+
+class QRhiResourceUpdateBatchPrivate
+{
+public:
+ struct DynamicBufferUpdate {
+ DynamicBufferUpdate() { }
+ DynamicBufferUpdate(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
+ : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_)
+ { }
+
+ QRhiBuffer *buf = nullptr;
+ int offset = 0;
+ QByteArray data;
+ };
+
+ struct StaticBufferUpload {
+ StaticBufferUpload() { }
+ StaticBufferUpload(QRhiBuffer *buf_, int offset_, int size_, const void *data_)
+ : buf(buf_), offset(offset_), data(reinterpret_cast<const char *>(data_), size_ ? size_ : buf_->size())
+ { }
+
+ QRhiBuffer *buf = nullptr;
+ int offset = 0;
+ QByteArray data;
+ };
+
+ struct TextureOp {
+ enum Type {
+ Upload,
+ Copy,
+ Read,
+ MipGen
+ };
+ Type type;
+ struct SUpload {
+ QRhiTexture *tex = nullptr;
+ // Specifying multiple uploads for a subresource must be supported.
+ // In the backend this can then end up, where applicable, as a
+ // single, batched copy operation with only one set of barriers.
+ // This helps when doing for example glyph cache fills.
+ QVector<QRhiTextureSubresourceUploadDescription> subresDesc[QRhi::MAX_LAYERS][QRhi::MAX_LEVELS];
+ } upload;
+ struct SCopy {
+ QRhiTexture *dst = nullptr;
+ QRhiTexture *src = nullptr;
+ QRhiTextureCopyDescription desc;
+ } copy;
+ struct SRead {
+ QRhiReadbackDescription rb;
+ QRhiReadbackResult *result;
+ } read;
+ struct SMipGen {
+ QRhiTexture *tex = nullptr;
+ int layer = 0;
+ } mipgen;
+
+ static TextureOp textureUpload(QRhiTexture *tex, const QRhiTextureUploadDescription &desc)
+ {
+ TextureOp op;
+ op.type = Upload;
+ op.upload.tex = tex;
+ const QVector<QRhiTextureUploadEntry> &entries(desc.entries());
+ for (const QRhiTextureUploadEntry &entry : entries)
+ op.upload.subresDesc[entry.layer()][entry.level()].append(entry.description());
+ return op;
+ }
+
+ static TextureOp textureCopy(QRhiTexture *dst, QRhiTexture *src, const QRhiTextureCopyDescription &desc)
+ {
+ TextureOp op;
+ op.type = Copy;
+ op.copy.dst = dst;
+ op.copy.src = src;
+ op.copy.desc = desc;
+ return op;
+ }
+
+ static TextureOp textureRead(const QRhiReadbackDescription &rb, QRhiReadbackResult *result)
+ {
+ TextureOp op;
+ op.type = Read;
+ op.read.rb = rb;
+ op.read.result = result;
+ return op;
+ }
+
+ static TextureOp textureMipGen(QRhiTexture *tex, int layer)
+ {
+ TextureOp op;
+ op.type = MipGen;
+ op.mipgen.tex = tex;
+ op.mipgen.layer = layer;
+ return op;
+ }
+ };
+
+ QVector<DynamicBufferUpdate> dynamicBufferUpdates;
+ QVector<StaticBufferUpload> staticBufferUploads;
+ QVector<TextureOp> textureOps;
+
+ QRhiResourceUpdateBatch *q = nullptr;
+ QRhiImplementation *rhi = nullptr;
+ int poolIndex = -1;
+
+ void free();
+ void merge(QRhiResourceUpdateBatchPrivate *other);
+
+ static QRhiResourceUpdateBatchPrivate *get(QRhiResourceUpdateBatch *b) { return b->d; }
+};
+
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::StaticBufferUpload, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiResourceUpdateBatchPrivate::TextureOp, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QRhiShaderResourceBindingPrivate
+{
+public:
+ QRhiShaderResourceBindingPrivate()
+ : ref(1)
+ {
+ }
+
+ QRhiShaderResourceBindingPrivate(const QRhiShaderResourceBindingPrivate *other)
+ : ref(1),
+ binding(other->binding),
+ stage(other->stage),
+ type(other->type),
+ u(other->u)
+ {
+ }
+
+ static QRhiShaderResourceBindingPrivate *get(QRhiShaderResourceBinding *s) { return s->d; }
+ static const QRhiShaderResourceBindingPrivate *get(const QRhiShaderResourceBinding *s) { return s->d; }
+
+ QAtomicInt ref;
+ int binding;
+ QRhiShaderResourceBinding::StageFlags stage;
+ QRhiShaderResourceBinding::Type type;
+ struct UniformBufferData {
+ QRhiBuffer *buf;
+ int offset;
+ int maybeSize;
+ bool hasDynamicOffset;
+ };
+ struct SampledTextureData {
+ QRhiTexture *tex;
+ QRhiSampler *sampler;
+ };
+ struct StorageImageData {
+ QRhiTexture *tex;
+ int level;
+ };
+ struct StorageBufferData {
+ QRhiBuffer *buf;
+ int offset;
+ int maybeSize;
+ };
+ union {
+ UniformBufferData ubuf;
+ SampledTextureData stex;
+ StorageImageData simage;
+ StorageBufferData sbuf;
+ } u;
+};
+
+template<typename T>
+struct QRhiBatchedBindings
+{
+ void feed(int binding, T resource) { // binding must be strictly increasing
+ if (curBinding == -1 || binding > curBinding + 1) {
+ finish();
+ curBatch.startBinding = binding;
+ curBatch.resources.clear();
+ curBatch.resources.append(resource);
+ } else {
+ Q_ASSERT(binding == curBinding + 1);
+ curBatch.resources.append(resource);
+ }
+ curBinding = binding;
+ }
+
+ void finish() {
+ if (!curBatch.resources.isEmpty())
+ batches.append(curBatch);
+ }
+
+ void clear() {
+ batches.clear();
+ curBatch.resources.clear();
+ curBinding = -1;
+ }
+
+ struct Batch {
+ uint startBinding;
+ QVarLengthArray<T, 4> resources;
+
+ bool operator==(const Batch &other) const
+ {
+ return startBinding == other.startBinding && resources == other.resources;
+ }
+
+ bool operator!=(const Batch &other) const
+ {
+ return !operator==(other);
+ }
+ };
+
+ QVarLengthArray<Batch, 4> batches; // sorted by startBinding
+
+ bool operator==(const QRhiBatchedBindings<T> &other) const
+ {
+ return batches == other.batches;
+ }
+
+ bool operator!=(const QRhiBatchedBindings<T> &other) const
+ {
+ return !operator==(other);
+ }
+
+private:
+ Batch curBatch;
+ int curBinding = -1;
+};
+
+class QRhiGlobalObjectIdGenerator
+{
+public:
+#ifdef Q_ATOMIC_INT64_IS_SUPPORTED
+ using Type = quint64;
+#else
+ using Type = quint32;
+#endif
+ static Type newId();
+};
+
+class QRhiPassResourceTracker
+{
+public:
+ bool isEmpty() const;
+ void reset();
+
+ struct UsageState {
+ int layout;
+ int access;
+ int stage;
+ };
+
+ enum BufferStage {
+ BufVertexInputStage,
+ BufVertexStage,
+ BufFragmentStage,
+ BufComputeStage
+ };
+
+ enum BufferAccess {
+ BufVertexInput,
+ BufIndexRead,
+ BufUniformRead,
+ BufStorageLoad,
+ BufStorageStore,
+ BufStorageLoadStore
+ };
+
+ void registerBuffer(QRhiBuffer *buf, int slot, BufferAccess *access, BufferStage *stage,
+ const UsageState &state);
+
+ enum TextureStage {
+ TexVertexStage,
+ TexFragmentStage,
+ TexColorOutputStage,
+ TexDepthOutputStage,
+ TexComputeStage
+ };
+
+ enum TextureAccess {
+ TexSample,
+ TexColorOutput,
+ TexDepthOutput,
+ TexStorageLoad,
+ TexStorageStore,
+ TexStorageLoadStore
+ };
+
+ void registerTexture(QRhiTexture *tex, TextureAccess *access, TextureStage *stage,
+ const UsageState &state);
+
+ struct Buffer {
+ QRhiBuffer *buf;
+ int slot;
+ BufferAccess access;
+ BufferStage stage;
+ UsageState stateAtPassBegin;
+ };
+ const QVector<Buffer> *buffers() const { return &m_buffers; }
+
+ struct Texture {
+ QRhiTexture *tex;
+ TextureAccess access;
+ TextureStage stage;
+ UsageState stateAtPassBegin;
+ };
+ const QVector<Texture> *textures() const { return &m_textures; }
+
+private:
+ QVector<Buffer> m_buffers;
+ QVector<Texture> m_textures;
+};
+
+Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Buffer, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiPassResourceTracker::Texture, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
new file mode 100644
index 0000000000..a8a490eb5c
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -0,0 +1,3803 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhid3d11_p_p.h"
+#include "qshader_p.h"
+#include <QWindow>
+#include <QOperatingSystemVersion>
+#include <qmath.h>
+#include <private/qsystemlibrary_p.h>
+
+#include <d3dcompiler.h>
+#include <comdef.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Direct3D 11 backend. Provides a double-buffered flip model (FLIP_DISCARD)
+ swapchain. Textures and "static" buffers are USAGE_DEFAULT, leaving it to
+ UpdateSubResource to upload the data in any way it sees fit. "Dynamic"
+ buffers are USAGE_DYNAMIC and updating is done by mapping with WRITE_DISCARD.
+ (so here QRhiBuffer keeps a copy of the buffer contents and all of it is
+ memcpy'd every time, leaving the rest (juggling with the memory area Map
+ returns) to the driver).
+*/
+
+/*!
+ \class QRhiD3D11InitParams
+ \inmodule QtRhi
+ \brief Direct3D 11 specific initialization parameters.
+
+ A D3D11-based QRhi needs no special parameters for initialization. If
+ desired, enableDebugLayer can be set to \c true to enable the Direct3D
+ debug layer. This can be useful during development, but should be avoided
+ in production builds.
+
+ \badcode
+ QRhiD3D11InitParams params;
+ params.enableDebugLayer = true;
+ rhi = QRhi::create(QRhi::D3D11, &params);
+ \endcode
+
+ \note QRhiSwapChain should only be used in combination with QWindow
+ instances that have their surface type set to QSurface::OpenGLSurface.
+ There are currently no Direct3D specifics in the Windows platform support
+ of Qt and therefore there is no separate QSurface type available.
+
+ \section2 Working with existing Direct3D 11 devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Direct3D device. This can be
+ achieved by passing a pointer to a QRhiD3D11NativeHandles to
+ QRhi::create(). Both the device and the device context must be set to a
+ non-null value then.
+
+ The QRhi does not take ownership of any of the external objects.
+
+ \note QRhi works with immediate contexts only. Deferred contexts are not
+ used in any way.
+
+ \note Regardless of using an imported or a QRhi-created device context, the
+ \c ID3D11DeviceContext1 interface (Direct3D 11.1) must be supported.
+ Initialization will fail otherwise.
+ */
+
+/*!
+ \class QRhiD3D11NativeHandles
+ \inmodule QtRhi
+ \brief Holds the D3D device and device context used by the QRhi.
+
+ \note The class uses \c{void *} as the type since including the COM-based
+ \c{d3d11.h} headers is not acceptable here. The actual types are
+ \c{ID3D11Device *} and \c{ID3D11DeviceContext *}.
+ */
+
+/*!
+ \class QRhiD3D11TextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the D3D texture object that is backing a QRhiTexture instance.
+
+ \note The class uses \c{void *} as the type since including the COM-based
+ \c{d3d11.h} headers is not acceptable here. The actual type is
+ \c{ID3D11Texture2D *}.
+ */
+
+QRhiD3D11::QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice)
+ : ofr(this)
+{
+ debugLayer = params->enableDebugLayer;
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ dev = reinterpret_cast<ID3D11Device *>(importDevice->dev);
+ if (dev) {
+ ID3D11DeviceContext *ctx = reinterpret_cast<ID3D11DeviceContext *>(importDevice->context);
+ if (SUCCEEDED(ctx->QueryInterface(IID_ID3D11DeviceContext1, reinterpret_cast<void **>(&context)))) {
+ // get rid of the ref added by QueryInterface
+ ctx->Release();
+ } else {
+ qWarning("ID3D11DeviceContext1 not supported by context, cannot import");
+ importedDevice = false;
+ }
+ } else {
+ qWarning("No ID3D11Device given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+static QString comErrorMessage(HRESULT hr)
+{
+#ifndef Q_OS_WINRT
+ const _com_error comError(hr);
+#else
+ const _com_error comError(hr, nullptr);
+#endif
+ QString result = QLatin1String("Error 0x") + QString::number(ulong(hr), 16);
+ if (const wchar_t *msg = comError.ErrorMessage())
+ result += QLatin1String(": ") + QString::fromWCharArray(msg);
+ return result;
+}
+
+template <class Int>
+static inline Int aligned(Int v, Int byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+bool QRhiD3D11::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+
+ uint devFlags = 0;
+ if (debugLayer)
+ devFlags |= D3D11_CREATE_DEVICE_DEBUG;
+
+ HRESULT hr;
+#if !defined(Q_CC_MINGW)
+ hasDxgi2 = QOperatingSystemVersion::current() > QOperatingSystemVersion::Windows7;
+ if (hasDxgi2)
+ hr = CreateDXGIFactory2(0, IID_IDXGIFactory2, reinterpret_cast<void **>(&dxgiFactory));
+ else
+#endif
+ hr = CreateDXGIFactory1(IID_IDXGIFactory1, reinterpret_cast<void **>(&dxgiFactory));
+
+ if (FAILED(hr)) {
+ qWarning("Failed to create DXGI factory: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (!importedDevice) {
+ IDXGIAdapter1 *adapterToUse = nullptr;
+ IDXGIAdapter1 *adapter;
+ int requestedAdapterIndex = -1;
+ if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
+ requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
+ for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+ DXGI_ADAPTER_DESC1 desc;
+ adapter->GetDesc1(&desc);
+ const QString name = QString::fromUtf16((char16_t *) desc.Description);
+ qDebug("Adapter %d: '%s' (flags 0x%x)", adapterIndex, qPrintable(name), desc.Flags);
+ if (!adapterToUse && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
+ adapterToUse = adapter;
+ qDebug(" using this adapter");
+ } else {
+ adapter->Release();
+ }
+ }
+ if (!adapterToUse) {
+ qWarning("No adapter");
+ return false;
+ }
+
+ ID3D11DeviceContext *ctx = nullptr;
+ HRESULT hr = D3D11CreateDevice(adapterToUse, D3D_DRIVER_TYPE_UNKNOWN, nullptr, devFlags,
+ nullptr, 0, D3D11_SDK_VERSION,
+ &dev, &featureLevel, &ctx);
+ adapterToUse->Release();
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 device and context: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (SUCCEEDED(ctx->QueryInterface(IID_ID3D11DeviceContext1, reinterpret_cast<void **>(&context)))) {
+ ctx->Release();
+ } else {
+ qWarning("ID3D11DeviceContext1 not supported");
+ return false;
+ }
+ } else {
+ Q_ASSERT(dev && context);
+ featureLevel = dev->GetFeatureLevel();
+ }
+
+ if (FAILED(context->QueryInterface(IID_ID3DUserDefinedAnnotation, reinterpret_cast<void **>(&annotations))))
+ annotations = nullptr;
+
+ nativeHandlesStruct.dev = dev;
+ nativeHandlesStruct.context = context;
+
+ return true;
+}
+
+void QRhiD3D11::destroy()
+{
+ finishActiveReadbacks();
+
+ if (annotations) {
+ annotations->Release();
+ annotations = nullptr;
+ }
+
+ if (!importedDevice) {
+ if (context) {
+ context->Release();
+ context = nullptr;
+ }
+ if (dev) {
+ dev->Release();
+ dev = nullptr;
+ }
+ }
+
+ if (dxgiFactory) {
+ dxgiFactory->Release();
+ dxgiFactory = nullptr;
+ }
+}
+
+void QRhiD3D11::reportLiveObjects(ID3D11Device *device)
+{
+ // this works only when params.enableDebugLayer was true
+ ID3D11Debug *debug;
+ if (SUCCEEDED(device->QueryInterface(IID_ID3D11Debug, reinterpret_cast<void **>(&debug)))) {
+ debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
+ debug->Release();
+ }
+}
+
+QVector<int> QRhiD3D11::supportedSampleCounts() const
+{
+ return { 1, 2, 4, 8 };
+}
+
+DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleCount(int sampleCount) const
+{
+ DXGI_SAMPLE_DESC desc;
+ desc.Count = 1;
+ desc.Quality = 0;
+
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ int s = qBound(1, sampleCount, 64);
+
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return desc;
+ }
+
+ desc.Count = s;
+ if (s > 1)
+ desc.Quality = D3D11_STANDARD_MULTISAMPLE_PATTERN;
+ else
+ desc.Quality = 0;
+
+ return desc;
+}
+
+QRhiSwapChain *QRhiD3D11::createSwapChain()
+{
+ return new QD3D11SwapChain(this);
+}
+
+QRhiBuffer *QRhiD3D11::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QD3D11Buffer(this, type, usage, size);
+}
+
+int QRhiD3D11::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiD3D11::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiD3D11::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiD3D11::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiD3D11::clipSpaceCorrMatrix() const
+{
+ // Like with Vulkan, but Y is already good.
+
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiD3D11::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(flags);
+
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12)
+ return false;
+
+ return true;
+}
+
+bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return annotations != nullptr;
+ case QRhi::Timestamps:
+ return true;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return true;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return false; // because UpdateSubresource cannot deal with this
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ case QRhi::Compute:
+ return true;
+ case QRhi::WideLines:
+ return false;
+ case QRhi::VertexShaderPointSize:
+ return false;
+ case QRhi::BaseVertex:
+ return true;
+ case QRhi::BaseInstance:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiD3D11::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+void QRhiD3D11::makeThreadLocalNativeContextCurrent()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QD3D11RenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiD3D11::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QD3D11Texture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiD3D11::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QD3D11Sampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QD3D11TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiD3D11::createGraphicsPipeline()
+{
+ return new QD3D11GraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiD3D11::createComputePipeline()
+{
+ return new QD3D11ComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiD3D11::createShaderResourceBindings()
+{
+ return new QD3D11ShaderResourceBindings(this);
+}
+
+void QRhiD3D11::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+ QD3D11GraphicsPipeline *psD = QRHI_RES(QD3D11GraphicsPipeline, ps);
+ const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+ if (pipelineChanged) {
+ cbD->currentGraphicsPipeline = ps;
+ cbD->currentComputePipeline = nullptr;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindGraphicsPipeline;
+ cmd.args.bindGraphicsPipeline.ps = psD;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass != QD3D11CommandBuffer::NoPass);
+ QD3D11GraphicsPipeline *gfxPsD = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+ QD3D11ComputePipeline *compPsD = QRHI_RES(QD3D11ComputePipeline, cbD->currentComputePipeline);
+
+ if (!srb) {
+ if (gfxPsD)
+ srb = gfxPsD->m_shaderResourceBindings;
+ else
+ srb = compPsD->m_shaderResourceBindings;
+ }
+
+ QD3D11ShaderResourceBindings *srbD = QRHI_RES(QD3D11ShaderResourceBindings, srb);
+
+ bool hasDynamicOffsetInSrb = false;
+ bool srbUpdate = false;
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ srbUpdate = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
+ QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ srbUpdate = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex);
+ if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
+ srbUpdate = true;
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf);
+ if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
+ srbUpdate = true;
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (srbUpdate)
+ updateShaderResourceBindings(srbD);
+
+ const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+ const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
+
+ if (srbChanged || srbRebuilt || srbUpdate || hasDynamicOffsetInSrb) {
+ if (gfxPsD) {
+ cbD->currentGraphicsSrb = srb;
+ cbD->currentComputeSrb = nullptr;
+ } else {
+ cbD->currentGraphicsSrb = nullptr;
+ cbD->currentComputeSrb = srb;
+ }
+ cbD->currentSrbGeneration = srbD->generation;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindShaderResources;
+ cmd.args.bindShaderResources.srb = srbD;
+ // dynamic offsets have to be applied at the time of executing the bind
+ // operations, not here
+ cmd.args.bindShaderResources.offsetOnlyChange = !srbChanged && !srbRebuilt && !srbUpdate && hasDynamicOffsetInSrb;
+ cmd.args.bindShaderResources.dynamicOffsetCount = 0;
+ if (hasDynamicOffsetInSrb) {
+ if (dynamicOffsetCount < QD3D11CommandBuffer::Command::MAX_UBUF_BINDINGS) {
+ cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
+ uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ const uint binding = dynOfs.first;
+ Q_ASSERT(aligned(dynOfs.second, quint32(256)) == dynOfs.second);
+ const uint offsetInConstants = dynOfs.second / 16;
+ *p++ = binding;
+ *p++ = offsetInConstants;
+ }
+ } else {
+ qWarning("Too many dynamic offsets (%d, max is %d)",
+ dynamicOffsetCount, QD3D11CommandBuffer::Command::MAX_UBUF_BINDINGS);
+ }
+ }
+
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiD3D11::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ bool needsBindVBuf = false;
+ for (int i = 0; i < bindingCount; ++i) {
+ const int inputSlot = startBinding + i;
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ if (cbD->currentVertexBuffers[inputSlot] != bufD->buffer
+ || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+ {
+ needsBindVBuf = true;
+ cbD->currentVertexBuffers[inputSlot] = bufD->buffer;
+ cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+ }
+ }
+
+ if (needsBindVBuf) {
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindVertexBuffers;
+ cmd.args.bindVertexBuffers.startSlot = startBinding;
+ cmd.args.bindVertexBuffers.slotCount = bindingCount;
+ const QVector<QRhiVertexInputBinding> inputBindings =
+ QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline)->m_vertexInputLayout.bindings();
+ for (int i = 0, ie = qMin(bindingCount, inputBindings.count()); i != ie; ++i) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+ cmd.args.bindVertexBuffers.buffers[i] = bufD->buffer;
+ cmd.args.bindVertexBuffers.offsets[i] = bindings[i].second;
+ cmd.args.bindVertexBuffers.strides[i] = inputBindings[i].stride();
+ }
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QD3D11Buffer *ibufD = QRHI_RES(QD3D11Buffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ if (ibufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(ibufD);
+
+ const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT
+ : DXGI_FORMAT_R32_UINT;
+ if (cbD->currentIndexBuffer != ibufD->buffer
+ || cbD->currentIndexOffset != indexOffset
+ || cbD->currentIndexFormat != dxgiFormat)
+ {
+ cbD->currentIndexBuffer = ibufD->buffer;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = dxgiFormat;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
+ cmd.args.bindIndexBuffer.offset = indexOffset;
+ cmd.args.bindIndexBuffer.format = dxgiFormat;
+ cbD->commands.append(cmd);
+ }
+ }
+}
+
+void QRhiD3D11::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+ Q_ASSERT(cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Viewport;
+
+ // d3d expects top-left, QRhiViewport is bottom-left
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ cmd.args.viewport.x = x;
+ cmd.args.viewport.y = y;
+ cmd.args.viewport.w = w;
+ cmd.args.viewport.h = h;
+ cmd.args.viewport.d0 = viewport.minDepth();
+ cmd.args.viewport.d1 = viewport.maxDepth();
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+ Q_ASSERT(cbD->currentTarget);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Scissor;
+
+ // d3d expects top-left, QRhiScissor is bottom-left
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ cmd.args.scissor.x = x;
+ cmd.args.scissor.y = y;
+ cmd.args.scissor.w = w;
+ cmd.args.scissor.h = h;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BlendConstants;
+ cmd.args.blendConstants.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+ cmd.args.blendConstants.c[0] = c.redF();
+ cmd.args.blendConstants.c[1] = c.greenF();
+ cmd.args.blendConstants.c[2] = c.blueF();
+ cmd.args.blendConstants.c[3] = c.alphaF();
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::StencilRef;
+ cmd.args.stencilRef.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+ cmd.args.stencilRef.ref = refValue;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Draw;
+ cmd.args.draw.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.instanceCount = instanceCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cmd.args.draw.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.instanceCount = instanceCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cmd.args.drawIndexed.vertexOffset = vertexOffset;
+ cmd.args.drawIndexed.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkBegin;
+ strncpy(cmd.args.debugMark.s, name.constData(), sizeof(cmd.args.debugMark.s));
+ cmd.args.debugMark.s[sizeof(cmd.args.debugMark.s) - 1] = '\0';
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkEnd;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers || !annotations)
+ return;
+
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkMsg;
+ strncpy(cmd.args.debugMark.s, msg.constData(), sizeof(cmd.args.debugMark.s));
+ cmd.args.debugMark.s[sizeof(cmd.args.debugMark.s) - 1] = '\0';
+ cbD->commands.append(cmd);
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiD3D11::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ flushCommandBuffer();
+}
+
+void QRhiD3D11::endExternal(QRhiCommandBuffer *cb)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->commands.isEmpty());
+ cbD->resetCachedState();
+ if (cbD->currentTarget) { // could be compute, no rendertarget then
+ QD3D11CommandBuffer::Command fbCmd;
+ fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget;
+ fbCmd.args.setRenderTarget.rt = cbD->currentTarget;
+ cbD->commands.append(fbCmd);
+ }
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+
+ QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+ contextState.currentSwapChain = swapChainD;
+ const int currentFrameSlot = swapChainD->currentFrameSlot;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ if (swapChainD->timestampActive[currentFrameSlot]) {
+ ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx];
+ ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1];
+ quint64 timestamps[2];
+ D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+ bool ok = true;
+ ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ ok &= context->GetData(tsEnd, &timestamps[1], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ // this above is often not ready, not even in frame_where_recorded+2,
+ // not clear why. so make the whole thing async and do not touch the
+ // queries until they are finally all available in frame this+2 or
+ // this+4 or ...
+ ok &= context->GetData(tsStart, &timestamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+ if (ok) {
+ if (!dj.Disjoint && dj.Frequency) {
+ const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
+ // finally got a value, just report it, the profiler cares about min/max/avg anyway
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs));
+ }
+ swapChainD->timestampActive[currentFrameSlot] = false;
+ } // else leave timestampActive set to true, will retry in a subsequent beginFrame
+ }
+
+ swapChainD->cb.resetState();
+
+ swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ?
+ swapChainD->msaaRtv[currentFrameSlot] : swapChainD->rtv[currentFrameSlot];
+ swapChainD->rt.d.dsv = swapChainD->ds ? swapChainD->ds->dsv : nullptr;
+
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+ Q_ASSERT(contextState.currentSwapChain = swapChainD);
+ const int currentFrameSlot = swapChainD->currentFrameSlot;
+
+ ID3D11Query *tsDisjoint = swapChainD->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = swapChainD->timestampQuery[tsIdx];
+ ID3D11Query *tsEnd = swapChainD->timestampQuery[tsIdx + 1];
+ const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestampActive[currentFrameSlot];
+
+ // send all commands to the context
+ if (recordTimestamps)
+ executeCommandBuffer(&swapChainD->cb, swapChainD);
+ else
+ executeCommandBuffer(&swapChainD->cb);
+
+ if (swapChainD->sampleDesc.Count > 1) {
+ context->ResolveSubresource(swapChainD->tex[currentFrameSlot], 0,
+ swapChainD->msaaTex[currentFrameSlot], 0,
+ swapChainD->colorFormat);
+ }
+
+ // this is here because we want to include the time spent on the resolve as well
+ if (recordTimestamps) {
+ context->End(tsEnd);
+ context->End(tsDisjoint);
+ swapChainD->timestampActive[currentFrameSlot] = true;
+ }
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the Present
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (!flags.testFlag(QRhi::SkipPresent)) {
+ const UINT presentFlags = 0;
+ HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
+ if (FAILED(hr))
+ qWarning("Failed to present: %s", qPrintable(comErrorMessage(hr)));
+
+ // move on to the next buffer
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D11SwapChain::BUFFER_COUNT;
+ } else {
+ context->Flush();
+ }
+
+ swapChainD->frameCount += 1;
+ contextState.currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ ofr.active = true;
+
+ ofr.cbWrapper.resetState();
+ *cb = &ofr.cbWrapper;
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame()
+{
+ ofr.active = false;
+
+ executeCommandBuffer(&ofr.cbWrapper);
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+ case QRhiTexture::BGRA8:
+ return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM;
+ case QRhiTexture::R8:
+ return DXGI_FORMAT_R8_UNORM;
+ case QRhiTexture::R16:
+ return DXGI_FORMAT_R16_UNORM;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return DXGI_FORMAT_R8_UNORM;
+
+ case QRhiTexture::RGBA16F:
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ case QRhiTexture::RGBA32F:
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+
+ case QRhiTexture::D16:
+ return DXGI_FORMAT_R16_TYPELESS;
+ case QRhiTexture::D32F:
+ return DXGI_FORMAT_R32_TYPELESS;
+
+ case QRhiTexture::BC1:
+ return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM;
+ case QRhiTexture::BC2:
+ return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM;
+ case QRhiTexture::BC3:
+ return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM;
+ case QRhiTexture::BC4:
+ return DXGI_FORMAT_BC4_UNORM;
+ case QRhiTexture::BC5:
+ return DXGI_FORMAT_BC5_UNORM;
+ case QRhiTexture::BC6H:
+ return DXGI_FORMAT_BC6H_UF16;
+ case QRhiTexture::BC7:
+ return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM;
+
+ case QRhiTexture::ETC2_RGB8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ETC2_RGB8A1:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ETC2_RGBA8:
+ qWarning("QRhiD3D11 does not support ETC2 textures");
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ case QRhiTexture::ASTC_4x4:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_5x4:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_5x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_6x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_6x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_8x8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x5:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x6:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x8:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_10x10:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_12x10:
+ Q_FALLTHROUGH();
+ case QRhiTexture::ASTC_12x12:
+ qWarning("QRhiD3D11 does not support ASTC textures");
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
+static inline QRhiTexture::Format colorTextureFormatFromDxgiFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags)
+{
+ switch (format) {
+ case DXGI_FORMAT_R8G8B8A8_UNORM:
+ return QRhiTexture::RGBA8;
+ case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::RGBA8;
+ case DXGI_FORMAT_B8G8R8A8_UNORM:
+ return QRhiTexture::BGRA8;
+ case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::BGRA8;
+ case DXGI_FORMAT_R8_UNORM:
+ return QRhiTexture::R8;
+ case DXGI_FORMAT_R16_UNORM:
+ return QRhiTexture::R16;
+ default: // this cannot assert, must warn and return unknown
+ qWarning("DXGI_FORMAT %d is not a recognized uncompressed color format", format);
+ break;
+ }
+ return QRhiTexture::UnknownFormat;
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::Format::D32F:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+QRhi::FrameOpResult QRhiD3D11::finish()
+{
+ if (inFrame)
+ flushCommandBuffer();
+
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiD3D11::flushCommandBuffer()
+{
+ if (ofr.active) {
+ Q_ASSERT(!contextState.currentSwapChain);
+ executeCommandBuffer(&ofr.cbWrapper);
+ ofr.cbWrapper.resetCommands();
+ } else {
+ Q_ASSERT(contextState.currentSwapChain);
+ executeCommandBuffer(&contextState.currentSwapChain->cb); // no timestampSwapChain, in order to avoid timestamp mess
+ contextState.currentSwapChain->cb.resetCommands();
+ }
+}
+
+void QRhiD3D11::enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
+{
+ UINT subres = D3D11CalcSubresource(level, layer, texD->mipLevelCount);
+ const QPoint dp = subresDesc.destinationTopLeft();
+ D3D11_BOX box;
+ box.front = 0;
+ // back, right, bottom are exclusive
+ box.back = 1;
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+ cmd.args.updateSubRes.dst = texD->tex;
+ cmd.args.updateSubRes.dstSubRes = subres;
+
+ bool cmdValid = true;
+ if (!subresDesc.image().isNull()) {
+ QImage img = subresDesc.image();
+ QSize size = img.size();
+ int bpl = img.bytesPerLine();
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const QPoint sp = subresDesc.sourceTopLeft();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ if (img.depth() == 32) {
+ const int offset = sp.y() * img.bytesPerLine() + sp.x() * 4;
+ cmd.args.updateSubRes.src = cbD->retainImage(img) + offset;
+ } else {
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ bpl = img.bytesPerLine();
+ cmd.args.updateSubRes.src = cbD->retainImage(img);
+ }
+ } else {
+ cmd.args.updateSubRes.src = cbD->retainImage(img);
+ }
+ box.left = dp.x();
+ box.top = dp.y();
+ box.right = dp.x() + size.width();
+ box.bottom = dp.y() + size.height();
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, size, &bpl, nullptr, &blockDim);
+ // Everything must be a multiple of the block width and
+ // height, so e.g. a mip level of size 2x2 will be 4x4 when it
+ // comes to the actual data.
+ box.left = aligned(dp.x(), blockDim.width());
+ box.top = aligned(dp.y(), blockDim.height());
+ box.right = aligned(dp.x() + size.width(), blockDim.width());
+ box.bottom = aligned(dp.y() + size.height(), blockDim.height());
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else if (!subresDesc.data().isEmpty()) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ QSize blockDim;
+ textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ box.left = dp.x();
+ box.top = dp.y();
+ box.right = dp.x() + size.width();
+ box.bottom = dp.y() + size.height();
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+ cmd.args.updateSubRes.srcRowPitch = bpl;
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ cmdValid = false;
+ }
+ if (cmdValid)
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ memcpy(bufD->dynBuf.data() + u.offset, u.data.constData(), u.data.size());
+ bufD->hasPendingDynamicUpdates = true;
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+ cmd.args.updateSubRes.dst = bufD->buffer;
+ cmd.args.updateSubRes.dstSubRes = 0;
+ cmd.args.updateSubRes.src = cbD->retainData(u.data);
+ cmd.args.updateSubRes.srcRowPitch = 0;
+ // Specify the region (even when offset is 0 and all data is provided)
+ // since the ID3D11Buffer's size is rounded up to be a multiple of 256
+ // while the data we have has the original size.
+ D3D11_BOX box;
+ box.left = u.offset;
+ box.top = box.front = 0;
+ box.back = box.bottom = 1;
+ box.right = u.offset + u.data.size(); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc
+ cmd.args.updateSubRes.hasDstBox = true;
+ cmd.args.updateSubRes.dstBox = box;
+ cbD->commands.append(cmd);
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.upload.tex);
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
+ }
+ }
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.copy.src);
+ QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.copy.dst);
+ UINT srcSubRes = D3D11CalcSubresource(u.copy.desc.sourceLevel(), u.copy.desc.sourceLayer(), srcD->mipLevelCount);
+ UINT dstSubRes = D3D11CalcSubresource(u.copy.desc.destinationLevel(), u.copy.desc.destinationLayer(), dstD->mipLevelCount);
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+ D3D11_BOX srcBox;
+ srcBox.left = sp.x();
+ srcBox.top = sp.y();
+ srcBox.front = 0;
+ // back, right, bottom are exclusive
+ srcBox.right = srcBox.left + size.width();
+ srcBox.bottom = srcBox.top + size.height();
+ srcBox.back = 1;
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+ cmd.args.copySubRes.dst = dstD->tex;
+ cmd.args.copySubRes.dstSubRes = dstSubRes;
+ cmd.args.copySubRes.dstX = dp.x();
+ cmd.args.copySubRes.dstY = dp.y();
+ cmd.args.copySubRes.src = srcD->tex;
+ cmd.args.copySubRes.srcSubRes = srcSubRes;
+ cmd.args.copySubRes.hasSrcBox = true;
+ cmd.args.copySubRes.srcBox = srcBox;
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ ActiveReadback aRb;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ ID3D11Resource *src;
+ DXGI_FORMAT dxgiFormat;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ UINT subres = 0;
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.read.rb.texture());
+ QD3D11SwapChain *swapChainD = nullptr;
+
+ if (texD) {
+ if (texD->sampleDesc.Count > 1) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ src = texD->tex;
+ dxgiFormat = texD->dxgiFormat;
+ pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize) : texD->m_pixelSize;
+ format = texD->m_format;
+ subres = D3D11CalcSubresource(u.read.rb.level(), u.read.rb.layer(), texD->mipLevelCount);
+ } else {
+ Q_ASSERT(contextState.currentSwapChain);
+ swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain);
+ if (swapChainD->sampleDesc.Count > 1) {
+ // Unlike with textures, reading back a multisample swapchain image
+ // has to be supported. Insert a resolve.
+ QD3D11CommandBuffer::Command rcmd;
+ rcmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+ rcmd.args.resolveSubRes.dst = swapChainD->tex[swapChainD->currentFrameSlot];
+ rcmd.args.resolveSubRes.dstSubRes = 0;
+ rcmd.args.resolveSubRes.src = swapChainD->msaaTex[swapChainD->currentFrameSlot];
+ rcmd.args.resolveSubRes.srcSubRes = 0;
+ rcmd.args.resolveSubRes.format = swapChainD->colorFormat;
+ cbD->commands.append(rcmd);
+ }
+ src = swapChainD->tex[swapChainD->currentFrameSlot];
+ dxgiFormat = swapChainD->colorFormat;
+ pixelSize = swapChainD->pixelSize;
+ format = colorTextureFormatFromDxgiFormat(dxgiFormat, nullptr);
+ if (format == QRhiTexture::UnknownFormat)
+ continue;
+ }
+ quint32 bufSize = 0;
+ quint32 bpl = 0;
+ textureFormatInfo(format, pixelSize, &bpl, &bufSize);
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = pixelSize.width();
+ desc.Height = pixelSize.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = dxgiFormat;
+ desc.SampleDesc.Count = 1;
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ ID3D11Texture2D *stagingTex;
+ HRESULT hr = dev->CreateTexture2D(&desc, nullptr, &stagingTex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create readback staging texture: %s", qPrintable(comErrorMessage(hr)));
+ return;
+ }
+ QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(stagingTex)),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ bufSize));
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+ cmd.args.copySubRes.dst = stagingTex;
+ cmd.args.copySubRes.dstSubRes = 0;
+ cmd.args.copySubRes.dstX = 0;
+ cmd.args.copySubRes.dstY = 0;
+ cmd.args.copySubRes.src = src;
+ cmd.args.copySubRes.srcSubRes = subres;
+ cmd.args.copySubRes.hasSrcBox = false;
+ cbD->commands.append(cmd);
+
+ aRb.stagingTex = stagingTex;
+ aRb.bufSize = bufSize;
+ aRb.bpl = bpl;
+ aRb.pixelSize = pixelSize;
+ aRb.format = format;
+
+ activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ Q_ASSERT(u.mipgen.tex->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::GenMip;
+ cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.mipgen.tex)->srv;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ ud->free();
+}
+
+void QRhiD3D11::finishActiveReadbacks()
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiD3D11::ActiveReadback &aRb(activeReadbacks[i]);
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+
+ D3D11_MAPPED_SUBRESOURCE mp;
+ HRESULT hr = context->Map(aRb.stagingTex, 0, D3D11_MAP_READ, 0, &mp);
+ if (FAILED(hr)) {
+ qWarning("Failed to map readback staging texture: %s", qPrintable(comErrorMessage(hr)));
+ aRb.stagingTex->Release();
+ continue;
+ }
+ // nothing says the rows are tightly packed in the texture, must take
+ // the stride into account
+ char *dst = aRb.result->data.data();
+ char *src = static_cast<char *>(mp.pData);
+ for (int y = 0, h = aRb.pixelSize.height(); y != h; ++y) {
+ memcpy(dst, src, aRb.bpl);
+ dst += aRb.bpl;
+ src += mp.RowPitch;
+ }
+ context->Unmap(aRb.stagingTex, 0);
+
+ aRb.stagingTex->Release();
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.stagingTex))));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ activeReadbacks.removeAt(i);
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
+{
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ return &QRHI_RES(QD3D11ReferenceRenderTarget, rt)->d;
+ case QRhiResource::TextureRenderTarget:
+ return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
+ default:
+ Q_UNREACHABLE();
+ return nullptr;
+ }
+}
+
+void QRhiD3D11::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(QRHI_RES(QD3D11CommandBuffer, cb)->recordingPass == QD3D11CommandBuffer::NoPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ bool wantsColorClear = true;
+ bool wantsDsClear = true;
+ QD3D11RenderTargetData *rtD = rtData(rt);
+ if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
+ QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, rt);
+ wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+ wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+ }
+
+ QD3D11CommandBuffer::Command fbCmd;
+ fbCmd.cmd = QD3D11CommandBuffer::Command::ResetShaderResources;
+ cbD->commands.append(fbCmd);
+ fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget;
+ fbCmd.args.setRenderTarget.rt = rt;
+ cbD->commands.append(fbCmd);
+
+ QD3D11CommandBuffer::Command clearCmd;
+ clearCmd.cmd = QD3D11CommandBuffer::Command::Clear;
+ clearCmd.args.clear.rt = rt;
+ clearCmd.args.clear.mask = 0;
+ if (rtD->colorAttCount && wantsColorClear)
+ clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Color;
+ if (rtD->dsAttCount && wantsDsClear)
+ clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Depth | QD3D11CommandBuffer::Command::Stencil;
+
+ clearCmd.args.clear.c[0] = colorClearValue.redF();
+ clearCmd.args.clear.c[1] = colorClearValue.greenF();
+ clearCmd.args.clear.c[2] = colorClearValue.blueF();
+ clearCmd.args.clear.c[3] = colorClearValue.alphaF();
+ clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
+ clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
+ cbD->commands.append(clearCmd);
+
+ cbD->recordingPass = QD3D11CommandBuffer::RenderPass;
+ cbD->currentTarget = rt;
+
+ cbD->resetCachedShaderResourceState();
+}
+
+void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+ if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+ QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, cbD->currentTarget);
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ for (int att = 0, attCount = colorAttachments.count(); att != attCount; ++att) {
+ const QRhiColorAttachment &colorAtt(colorAttachments[att]);
+ if (!colorAtt.resolveTexture())
+ continue;
+
+ QD3D11Texture *dstTexD = QRHI_RES(QD3D11Texture, colorAtt.resolveTexture());
+ QD3D11Texture *srcTexD = QRHI_RES(QD3D11Texture, colorAtt.texture());
+ QD3D11RenderBuffer *srcRbD = QRHI_RES(QD3D11RenderBuffer, colorAtt.renderBuffer());
+ Q_ASSERT(srcTexD || srcRbD);
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+ cmd.args.resolveSubRes.dst = dstTexD->tex;
+ cmd.args.resolveSubRes.dstSubRes = D3D11CalcSubresource(colorAtt.resolveLevel(),
+ colorAtt.resolveLayer(),
+ dstTexD->mipLevelCount);
+ if (srcTexD) {
+ cmd.args.resolveSubRes.src = srcTexD->tex;
+ if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) {
+ qWarning("Resolve source and destination formats do not match");
+ continue;
+ }
+ if (srcTexD->sampleDesc.Count <= 1) {
+ qWarning("Cannot resolve a non-multisample texture");
+ continue;
+ }
+ if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) {
+ qWarning("Resolve source and destination sizes do not match");
+ continue;
+ }
+ } else {
+ cmd.args.resolveSubRes.src = srcRbD->tex;
+ if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) {
+ qWarning("Resolve source and destination formats do not match");
+ continue;
+ }
+ if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) {
+ qWarning("Resolve source and destination sizes do not match");
+ continue;
+ }
+ }
+ cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, colorAtt.layer(), 1);
+ cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ cbD->recordingPass = QD3D11CommandBuffer::NoPass;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::ResetShaderResources;
+ cbD->commands.append(cmd);
+
+ cbD->recordingPass = QD3D11CommandBuffer::ComputePass;
+
+ cbD->resetCachedShaderResourceState();
+}
+
+void QRhiD3D11::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+
+ cbD->recordingPass = QD3D11CommandBuffer::NoPass;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+ QD3D11ComputePipeline *psD = QRHI_RES(QD3D11ComputePipeline, ps);
+ const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+ if (pipelineChanged) {
+ cbD->currentGraphicsPipeline = nullptr;
+ cbD->currentComputePipeline = psD;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::BindComputePipeline;
+ cmd.args.bindComputePipeline.ps = psD;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiD3D11::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+
+ QD3D11CommandBuffer::Command cmd;
+ cmd.cmd = QD3D11CommandBuffer::Command::Dispatch;
+ cmd.args.dispatch.x = x;
+ cmd.args.dispatch.y = y;
+ cmd.args.dispatch.z = z;
+ cbD->commands.append(cmd);
+}
+
+void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD)
+{
+ srbD->vsubufs.clear();
+ srbD->vsubufoffsets.clear();
+ srbD->vsubufsizes.clear();
+
+ srbD->fsubufs.clear();
+ srbD->fsubufoffsets.clear();
+ srbD->fsubufsizes.clear();
+
+ srbD->csubufs.clear();
+ srbD->csubufoffsets.clear();
+ srbD->csubufsizes.clear();
+
+ srbD->vssamplers.clear();
+ srbD->vsshaderresources.clear();
+
+ srbD->fssamplers.clear();
+ srbD->fsshaderresources.clear();
+
+ srbD->cssamplers.clear();
+ srbD->csshaderresources.clear();
+
+ srbD->csUAVs.clear();
+
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+ Q_ASSERT(aligned(b->u.ubuf.offset, 256) == b->u.ubuf.offset);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ // dynamic ubuf offsets are not considered here, those are baked in
+ // at a later stage, which is good as vsubufoffsets and friends are
+ // per-srb, not per-setShaderResources call
+ const uint offsetInConstants = b->u.ubuf.offset / 16;
+ // size must be 16 mult. (in constants, i.e. multiple of 256 bytes).
+ // We can round up if needed since the buffers's actual size
+ // (ByteWidth) is always a multiple of 256.
+ const uint sizeInConstants = aligned(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size, 256) / 16;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ srbD->vsubufs.feed(b->binding, bufD->buffer);
+ srbD->vsubufoffsets.feed(b->binding, offsetInConstants);
+ srbD->vsubufsizes.feed(b->binding, sizeInConstants);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ srbD->fsubufs.feed(b->binding, bufD->buffer);
+ srbD->fsubufoffsets.feed(b->binding, offsetInConstants);
+ srbD->fsubufsizes.feed(b->binding, sizeInConstants);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ srbD->csubufs.feed(b->binding, bufD->buffer);
+ srbD->csubufoffsets.feed(b->binding, offsetInConstants);
+ srbD->csubufsizes.feed(b->binding, sizeInConstants);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ // A sampler with binding N is mapped to a HLSL sampler and texture
+ // with registers sN and tN by SPIRV-Cross.
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.stex.tex);
+ QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, b->u.stex.sampler);
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ srbD->vssamplers.feed(b->binding, samplerD->samplerState);
+ srbD->vsshaderresources.feed(b->binding, texD->srv);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ srbD->fssamplers.feed(b->binding, samplerD->samplerState);
+ srbD->fsshaderresources.feed(b->binding, texD->srv);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ srbD->cssamplers.feed(b->binding, samplerD->samplerState);
+ srbD->csshaderresources.feed(b->binding, texD->srv);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex);
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ ID3D11UnorderedAccessView *uav = texD->unorderedAccessViewForLevel(b->u.simage.level);
+ if (uav)
+ srbD->csUAVs.feed(b->binding, uav);
+ } else {
+ qWarning("Unordered access only supported at compute stage");
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf);
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ ID3D11UnorderedAccessView *uav = bufD->unorderedAccessView();
+ if (uav)
+ srbD->csUAVs.feed(b->binding, uav);
+ } else {
+ qWarning("Unordered access only supported at compute stage");
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ srbD->vsubufs.finish();
+ srbD->vsubufoffsets.finish();
+ srbD->vsubufsizes.finish();
+
+ srbD->fsubufs.finish();
+ srbD->fsubufoffsets.finish();
+ srbD->fsubufsizes.finish();
+
+ srbD->csubufs.finish();
+ srbD->csubufoffsets.finish();
+ srbD->csubufsizes.finish();
+
+ srbD->vssamplers.finish();
+ srbD->vsshaderresources.finish();
+
+ srbD->fssamplers.finish();
+ srbD->fsshaderresources.finish();
+
+ srbD->cssamplers.finish();
+ srbD->csshaderresources.finish();
+
+ srbD->csUAVs.finish();
+}
+
+void QRhiD3D11::executeBufferHostWritesForCurrentFrame(QD3D11Buffer *bufD)
+{
+ if (!bufD->hasPendingDynamicUpdates)
+ return;
+
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ bufD->hasPendingDynamicUpdates = false;
+ D3D11_MAPPED_SUBRESOURCE mp;
+ HRESULT hr = context->Map(bufD->buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
+ if (SUCCEEDED(hr)) {
+ memcpy(mp.pData, bufD->dynBuf.constData(), bufD->dynBuf.size());
+ context->Unmap(bufD->buffer, 0);
+ } else {
+ qWarning("Failed to map buffer: %s", qPrintable(comErrorMessage(hr)));
+ }
+}
+
+static void applyDynamicOffsets(QVarLengthArray<UINT, 4> *offsets,
+ int batchIndex,
+ QRhiBatchedBindings<ID3D11Buffer *> *ubufs,
+ QRhiBatchedBindings<UINT> *ubufoffsets,
+ const uint *dynOfsPairs, int dynOfsPairCount)
+{
+ const UINT count = ubufs->batches[batchIndex].resources.count();
+ const UINT startBinding = ubufs->batches[batchIndex].startBinding;
+ *offsets = ubufoffsets->batches[batchIndex].resources;
+ for (UINT b = 0; b < count; ++b) {
+ for (int di = 0; di < dynOfsPairCount; ++di) {
+ const uint binding = dynOfsPairs[2 * di];
+ if (binding == startBinding + b) {
+ const uint offsetInConstants = dynOfsPairs[2 * di + 1];
+ (*offsets)[b] = offsetInConstants;
+ break;
+ }
+ }
+ }
+}
+
+void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+ const uint *dynOfsPairs, int dynOfsPairCount,
+ bool offsetOnlyChange)
+{
+ if (!offsetOnlyChange) {
+ for (const auto &batch : srbD->vssamplers.batches)
+ context->VSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData());
+
+ for (const auto &batch : srbD->vsshaderresources.batches) {
+ context->VSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData());
+ contextState.vsHighestActiveSrvBinding = qMax<int>(contextState.vsHighestActiveSrvBinding,
+ batch.startBinding + batch.resources.count() - 1);
+ }
+
+ for (const auto &batch : srbD->fssamplers.batches)
+ context->PSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData());
+
+ for (const auto &batch : srbD->fsshaderresources.batches) {
+ context->PSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData());
+ contextState.fsHighestActiveSrvBinding = qMax<int>(contextState.fsHighestActiveSrvBinding,
+ batch.startBinding + batch.resources.count() - 1);
+ }
+
+ for (const auto &batch : srbD->cssamplers.batches)
+ context->CSSetSamplers(batch.startBinding, batch.resources.count(), batch.resources.constData());
+
+ for (const auto &batch : srbD->csshaderresources.batches) {
+ context->CSSetShaderResources(batch.startBinding, batch.resources.count(), batch.resources.constData());
+ contextState.csHighestActiveSrvBinding = qMax<int>(contextState.csHighestActiveSrvBinding,
+ batch.startBinding + batch.resources.count() - 1);
+ }
+ }
+
+ for (int i = 0, ie = srbD->vsubufs.batches.count(); i != ie; ++i) {
+ if (!dynOfsPairCount) {
+ context->VSSetConstantBuffers1(srbD->vsubufs.batches[i].startBinding,
+ srbD->vsubufs.batches[i].resources.count(),
+ srbD->vsubufs.batches[i].resources.constData(),
+ srbD->vsubufoffsets.batches[i].resources.constData(),
+ srbD->vsubufsizes.batches[i].resources.constData());
+ } else {
+ QVarLengthArray<UINT, 4> offsets;
+ applyDynamicOffsets(&offsets, i, &srbD->vsubufs, &srbD->vsubufoffsets, dynOfsPairs, dynOfsPairCount);
+ context->VSSetConstantBuffers1(srbD->vsubufs.batches[i].startBinding,
+ srbD->vsubufs.batches[i].resources.count(),
+ srbD->vsubufs.batches[i].resources.constData(),
+ offsets.constData(),
+ srbD->vsubufsizes.batches[i].resources.constData());
+ }
+ }
+
+ for (int i = 0, ie = srbD->fsubufs.batches.count(); i != ie; ++i) {
+ if (!dynOfsPairCount) {
+ context->PSSetConstantBuffers1(srbD->fsubufs.batches[i].startBinding,
+ srbD->fsubufs.batches[i].resources.count(),
+ srbD->fsubufs.batches[i].resources.constData(),
+ srbD->fsubufoffsets.batches[i].resources.constData(),
+ srbD->fsubufsizes.batches[i].resources.constData());
+ } else {
+ QVarLengthArray<UINT, 4> offsets;
+ applyDynamicOffsets(&offsets, i, &srbD->fsubufs, &srbD->fsubufoffsets, dynOfsPairs, dynOfsPairCount);
+ context->PSSetConstantBuffers1(srbD->fsubufs.batches[i].startBinding,
+ srbD->fsubufs.batches[i].resources.count(),
+ srbD->fsubufs.batches[i].resources.constData(),
+ offsets.constData(),
+ srbD->fsubufsizes.batches[i].resources.constData());
+ }
+ }
+
+ for (int i = 0, ie = srbD->csubufs.batches.count(); i != ie; ++i) {
+ if (!dynOfsPairCount) {
+ context->CSSetConstantBuffers1(srbD->csubufs.batches[i].startBinding,
+ srbD->csubufs.batches[i].resources.count(),
+ srbD->csubufs.batches[i].resources.constData(),
+ srbD->csubufoffsets.batches[i].resources.constData(),
+ srbD->csubufsizes.batches[i].resources.constData());
+ } else {
+ QVarLengthArray<UINT, 4> offsets;
+ applyDynamicOffsets(&offsets, i, &srbD->csubufs, &srbD->csubufoffsets, dynOfsPairs, dynOfsPairCount);
+ context->CSSetConstantBuffers1(srbD->csubufs.batches[i].startBinding,
+ srbD->csubufs.batches[i].resources.count(),
+ srbD->csubufs.batches[i].resources.constData(),
+ offsets.constData(),
+ srbD->csubufsizes.batches[i].resources.constData());
+ }
+ }
+
+ for (int i = 0, ie = srbD->csUAVs.batches.count(); i != ie; ++i) {
+ const uint startBinding = srbD->csUAVs.batches[i].startBinding;
+ const uint count = srbD->csUAVs.batches[i].resources.count();
+ context->CSSetUnorderedAccessViews(startBinding,
+ count,
+ srbD->csUAVs.batches[i].resources.constData(),
+ nullptr);
+ contextState.csHighestActiveUavBinding = qMax<int>(contextState.csHighestActiveUavBinding,
+ startBinding + count - 1);
+ }
+}
+
+void QRhiD3D11::resetShaderResources()
+{
+ // Output cannot be bound on input etc.
+
+ if (contextState.vsHasIndexBufferBound) {
+ context->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
+ contextState.vsHasIndexBufferBound = false;
+ }
+
+ if (contextState.vsHighestActiveVertexBufferBinding >= 0) {
+ const int count = contextState.vsHighestActiveVertexBufferBinding + 1;
+ QVarLengthArray<ID3D11Buffer *, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullbufs(count);
+ for (int i = 0; i < count; ++i)
+ nullbufs[i] = nullptr;
+ QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullstrides(count);
+ for (int i = 0; i < count; ++i)
+ nullstrides[i] = 0;
+ QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nulloffsets(count);
+ for (int i = 0; i < count; ++i)
+ nulloffsets[i] = 0;
+ context->IASetVertexBuffers(0, count, nullbufs.constData(), nullstrides.constData(), nulloffsets.constData());
+ contextState.vsHighestActiveVertexBufferBinding = -1;
+ }
+
+ int nullsrvCount = qMax(contextState.vsHighestActiveSrvBinding, contextState.fsHighestActiveSrvBinding);
+ nullsrvCount = qMax(nullsrvCount, contextState.csHighestActiveSrvBinding);
+ nullsrvCount += 1;
+ if (nullsrvCount > 0) {
+ QVarLengthArray<ID3D11ShaderResourceView *,
+ D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nullsrvs(nullsrvCount);
+ for (int i = 0; i < nullsrvs.count(); ++i)
+ nullsrvs[i] = nullptr;
+ if (contextState.vsHighestActiveSrvBinding >= 0) {
+ context->VSSetShaderResources(0, contextState.vsHighestActiveSrvBinding + 1, nullsrvs.constData());
+ contextState.vsHighestActiveSrvBinding = -1;
+ }
+ if (contextState.fsHighestActiveSrvBinding >= 0) {
+ context->PSSetShaderResources(0, contextState.fsHighestActiveSrvBinding + 1, nullsrvs.constData());
+ contextState.fsHighestActiveSrvBinding = -1;
+ }
+ if (contextState.csHighestActiveSrvBinding >= 0) {
+ context->CSSetShaderResources(0, contextState.csHighestActiveSrvBinding + 1, nullsrvs.constData());
+ contextState.csHighestActiveSrvBinding = -1;
+ }
+ }
+
+ if (contextState.csHighestActiveUavBinding >= 0) {
+ const int nulluavCount = contextState.csHighestActiveUavBinding + 1;
+ QVarLengthArray<ID3D11UnorderedAccessView *,
+ D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nulluavs(nulluavCount);
+ for (int i = 0; i < nulluavCount; ++i)
+ nulluavs[i] = nullptr;
+ context->CSSetUnorderedAccessViews(0, nulluavCount, nulluavs.constData(), nullptr);
+ contextState.csHighestActiveUavBinding = -1;
+ }
+}
+
+void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain)
+{
+ quint32 stencilRef = 0;
+ float blendConstants[] = { 1, 1, 1, 1 };
+
+ if (timestampSwapChain) {
+ const int currentFrameSlot = timestampSwapChain->currentFrameSlot;
+ ID3D11Query *tsDisjoint = timestampSwapChain->timestampDisjointQuery[currentFrameSlot];
+ const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
+ ID3D11Query *tsStart = timestampSwapChain->timestampQuery[tsIdx];
+ if (tsDisjoint && tsStart && !timestampSwapChain->timestampActive[currentFrameSlot]) {
+ // The timestamps seem to include vsync time with Present(1), except
+ // when running on a non-primary gpu. This is not ideal. So try working
+ // it around by issuing a semi-fake OMSetRenderTargets early and
+ // writing the first timestamp only afterwards.
+ context->Begin(tsDisjoint);
+ QD3D11RenderTargetData *rtD = rtData(&timestampSwapChain->rt);
+ context->OMSetRenderTargets(rtD->colorAttCount, rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+ context->End(tsStart); // just record a timestamp, no Begin needed
+ }
+ }
+
+ for (const QD3D11CommandBuffer::Command &cmd : qAsConst(cbD->commands)) {
+ switch (cmd.cmd) {
+ case QD3D11CommandBuffer::Command::ResetShaderResources:
+ resetShaderResources();
+ break;
+ case QD3D11CommandBuffer::Command::SetRenderTarget:
+ {
+ QD3D11RenderTargetData *rtD = rtData(cmd.args.setRenderTarget.rt);
+ context->OMSetRenderTargets(rtD->colorAttCount, rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::Clear:
+ {
+ QD3D11RenderTargetData *rtD = rtData(cmd.args.clear.rt);
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Color) {
+ for (int i = 0; i < rtD->colorAttCount; ++i)
+ context->ClearRenderTargetView(rtD->rtv[i], cmd.args.clear.c);
+ }
+ uint ds = 0;
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Depth)
+ ds |= D3D11_CLEAR_DEPTH;
+ if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Stencil)
+ ds |= D3D11_CLEAR_STENCIL;
+ if (ds)
+ context->ClearDepthStencilView(rtD->dsv, ds, cmd.args.clear.d, cmd.args.clear.s);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::Viewport:
+ {
+ D3D11_VIEWPORT v;
+ v.TopLeftX = cmd.args.viewport.x;
+ v.TopLeftY = cmd.args.viewport.y;
+ v.Width = cmd.args.viewport.w;
+ v.Height = cmd.args.viewport.h;
+ v.MinDepth = cmd.args.viewport.d0;
+ v.MaxDepth = cmd.args.viewport.d1;
+ context->RSSetViewports(1, &v);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::Scissor:
+ {
+ D3D11_RECT r;
+ r.left = cmd.args.scissor.x;
+ r.top = cmd.args.scissor.y;
+ // right and bottom are exclusive
+ r.right = cmd.args.scissor.x + cmd.args.scissor.w;
+ r.bottom = cmd.args.scissor.y + cmd.args.scissor.h;
+ context->RSSetScissorRects(1, &r);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::BindVertexBuffers:
+ contextState.vsHighestActiveVertexBufferBinding = qMax<int>(
+ contextState.vsHighestActiveVertexBufferBinding,
+ cmd.args.bindVertexBuffers.startSlot + cmd.args.bindVertexBuffers.slotCount - 1);
+ context->IASetVertexBuffers(cmd.args.bindVertexBuffers.startSlot,
+ cmd.args.bindVertexBuffers.slotCount,
+ cmd.args.bindVertexBuffers.buffers,
+ cmd.args.bindVertexBuffers.strides,
+ cmd.args.bindVertexBuffers.offsets);
+ break;
+ case QD3D11CommandBuffer::Command::BindIndexBuffer:
+ contextState.vsHasIndexBufferBound = true;
+ context->IASetIndexBuffer(cmd.args.bindIndexBuffer.buffer,
+ cmd.args.bindIndexBuffer.format,
+ cmd.args.bindIndexBuffer.offset);
+ break;
+ case QD3D11CommandBuffer::Command::BindGraphicsPipeline:
+ {
+ QD3D11GraphicsPipeline *psD = cmd.args.bindGraphicsPipeline.ps;
+ context->VSSetShader(psD->vs, nullptr, 0);
+ context->PSSetShader(psD->fs, nullptr, 0);
+ context->IASetPrimitiveTopology(psD->d3dTopology);
+ context->IASetInputLayout(psD->inputLayout);
+ context->OMSetDepthStencilState(psD->dsState, stencilRef);
+ context->OMSetBlendState(psD->blendState, blendConstants, 0xffffffff);
+ context->RSSetState(psD->rastState);
+ }
+ break;
+ case QD3D11CommandBuffer::Command::BindShaderResources:
+ bindShaderResources(cmd.args.bindShaderResources.srb,
+ cmd.args.bindShaderResources.dynamicOffsetPairs,
+ cmd.args.bindShaderResources.dynamicOffsetCount,
+ cmd.args.bindShaderResources.offsetOnlyChange);
+ break;
+ case QD3D11CommandBuffer::Command::StencilRef:
+ stencilRef = cmd.args.stencilRef.ref;
+ context->OMSetDepthStencilState(cmd.args.stencilRef.ps->dsState, stencilRef);
+ break;
+ case QD3D11CommandBuffer::Command::BlendConstants:
+ memcpy(blendConstants, cmd.args.blendConstants.c, 4 * sizeof(float));
+ context->OMSetBlendState(cmd.args.blendConstants.ps->blendState, blendConstants, 0xffffffff);
+ break;
+ case QD3D11CommandBuffer::Command::Draw:
+ if (cmd.args.draw.ps) {
+ if (cmd.args.draw.instanceCount == 1)
+ context->Draw(cmd.args.draw.vertexCount, cmd.args.draw.firstVertex);
+ else
+ context->DrawInstanced(cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
+ cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
+ } else {
+ qWarning("No graphics pipeline active for draw; ignored");
+ }
+ break;
+ case QD3D11CommandBuffer::Command::DrawIndexed:
+ if (cmd.args.drawIndexed.ps) {
+ if (cmd.args.drawIndexed.instanceCount == 1)
+ context->DrawIndexed(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.firstIndex,
+ cmd.args.drawIndexed.vertexOffset);
+ else
+ context->DrawIndexedInstanced(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
+ cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
+ cmd.args.drawIndexed.firstInstance);
+ } else {
+ qWarning("No graphics pipeline active for drawIndexed; ignored");
+ }
+ break;
+ case QD3D11CommandBuffer::Command::UpdateSubRes:
+ context->UpdateSubresource(cmd.args.updateSubRes.dst, cmd.args.updateSubRes.dstSubRes,
+ cmd.args.updateSubRes.hasDstBox ? &cmd.args.updateSubRes.dstBox : nullptr,
+ cmd.args.updateSubRes.src, cmd.args.updateSubRes.srcRowPitch, 0);
+ break;
+ case QD3D11CommandBuffer::Command::CopySubRes:
+ context->CopySubresourceRegion(cmd.args.copySubRes.dst, cmd.args.copySubRes.dstSubRes,
+ cmd.args.copySubRes.dstX, cmd.args.copySubRes.dstY, 0,
+ cmd.args.copySubRes.src, cmd.args.copySubRes.srcSubRes,
+ cmd.args.copySubRes.hasSrcBox ? &cmd.args.copySubRes.srcBox : nullptr);
+ break;
+ case QD3D11CommandBuffer::Command::ResolveSubRes:
+ context->ResolveSubresource(cmd.args.resolveSubRes.dst, cmd.args.resolveSubRes.dstSubRes,
+ cmd.args.resolveSubRes.src, cmd.args.resolveSubRes.srcSubRes,
+ cmd.args.resolveSubRes.format);
+ break;
+ case QD3D11CommandBuffer::Command::GenMip:
+ context->GenerateMips(cmd.args.genMip.srv);
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkBegin:
+ annotations->BeginEvent(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkEnd:
+ annotations->EndEvent();
+ break;
+ case QD3D11CommandBuffer::Command::DebugMarkMsg:
+ annotations->SetMarker(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+ break;
+ case QD3D11CommandBuffer::Command::BindComputePipeline:
+ context->CSSetShader(cmd.args.bindComputePipeline.ps->cs, nullptr, 0);
+ break;
+ case QD3D11CommandBuffer::Command::Dispatch:
+ context->Dispatch(cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+QD3D11Buffer::QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QD3D11Buffer::~QD3D11Buffer()
+{
+ release();
+}
+
+void QD3D11Buffer::release()
+{
+ if (!buffer)
+ return;
+
+ dynBuf.clear();
+
+ buffer->Release();
+ buffer = nullptr;
+
+ if (uav) {
+ uav->Release();
+ uav = nullptr;
+ }
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline uint toD3DBufferUsage(QRhiBuffer::UsageFlags usage)
+{
+ int u = 0;
+ if (usage.testFlag(QRhiBuffer::VertexBuffer))
+ u |= D3D11_BIND_VERTEX_BUFFER;
+ if (usage.testFlag(QRhiBuffer::IndexBuffer))
+ u |= D3D11_BIND_INDEX_BUFFER;
+ if (usage.testFlag(QRhiBuffer::UniformBuffer))
+ u |= D3D11_BIND_CONSTANT_BUFFER;
+ if (usage.testFlag(QRhiBuffer::StorageBuffer))
+ u |= D3D11_BIND_UNORDERED_ACCESS;
+ return u;
+}
+
+bool QD3D11Buffer::build()
+{
+ if (buffer)
+ release();
+
+ if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) {
+ qWarning("UniformBuffer must always be combined with Dynamic on D3D11");
+ return false;
+ }
+
+ if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+ qWarning("StorageBuffer cannot be combined with Dynamic");
+ return false;
+ }
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+ const int roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256 : 4);
+
+ D3D11_BUFFER_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.ByteWidth = roundedSize;
+ desc.Usage = m_type == Dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
+ desc.BindFlags = toD3DBufferUsage(m_usage);
+ desc.CPUAccessFlags = m_type == Dynamic ? D3D11_CPU_ACCESS_WRITE : 0;
+ desc.MiscFlags = m_usage.testFlag(QRhiBuffer::StorageBuffer) ? D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS : 0;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateBuffer(&desc, nullptr, &buffer);
+ if (FAILED(hr)) {
+ qWarning("Failed to create buffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (m_type == Dynamic) {
+ dynBuf.resize(m_size);
+ hasPendingDynamicUpdates = false;
+ }
+
+ if (!m_objectName.isEmpty())
+ buffer->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, roundedSize, m_type == Dynamic ? 2 : 1, m_type == Dynamic ? 1 : 0));
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView()
+{
+ if (uav)
+ return uav;
+
+ // SPIRV-Cross generated HLSL uses RWByteAddressBuffer
+ D3D11_UNORDERED_ACCESS_VIEW_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Format = DXGI_FORMAT_R32_TYPELESS;
+ desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
+ desc.Buffer.FirstElement = 0;
+ desc.Buffer.NumElements = aligned(m_size, 4) / 4;
+ desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateUnorderedAccessView(buffer, &desc, &uav);
+ if (FAILED(hr)) {
+ qWarning("Failed to create UAV: %s", qPrintable(comErrorMessage(hr)));
+ return nullptr;
+ }
+
+ return uav;
+}
+
+QD3D11RenderBuffer::QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QD3D11RenderBuffer::~QD3D11RenderBuffer()
+{
+ release();
+}
+
+void QD3D11RenderBuffer::release()
+{
+ if (!tex)
+ return;
+
+ if (dsv) {
+ dsv->Release();
+ dsv = nullptr;
+ }
+
+ if (rtv) {
+ rtv->Release();
+ rtv = nullptr;
+ }
+
+ tex->Release();
+ tex = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QD3D11RenderBuffer::build()
+{
+ if (tex)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = m_pixelSize.width();
+ desc.Height = m_pixelSize.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+
+ if (m_type == Color) {
+ dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ desc.Format = dxgiFormat;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color renderbuffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = dxgiFormat;
+ rtvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS
+ : D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(tex, &rtvDesc, &rtv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else if (m_type == DepthStencil) {
+ dxgiFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
+ desc.Format = dxgiFormat;
+ desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create depth-stencil buffer: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
+ memset(&dsvDesc, 0, sizeof(dsvDesc));
+ dsvDesc.Format = dxgiFormat;
+ dsvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+ : D3D11_DSV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateDepthStencilView(tex, &dsvDesc, &dsv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create dsv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ if (!m_objectName.isEmpty())
+ tex->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, sampleDesc.Count));
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QD3D11RenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QD3D11Texture::QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ perLevelViews[i] = nullptr;
+}
+
+QD3D11Texture::~QD3D11Texture()
+{
+ release();
+}
+
+void QD3D11Texture::release()
+{
+ if (!tex)
+ return;
+
+ if (srv) {
+ srv->Release();
+ srv = nullptr;
+ }
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
+ if (perLevelViews[i]) {
+ perLevelViews[i]->Release();
+ perLevelViews[i] = nullptr;
+ }
+ }
+
+ if (owns)
+ tex->Release();
+
+ tex = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ return DXGI_FORMAT_R16_FLOAT;
+ case QRhiTexture::Format::D32F:
+ return DXGI_FORMAT_R32_FLOAT;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R32_FLOAT;
+ }
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ return DXGI_FORMAT_D16_UNORM;
+ case QRhiTexture::Format::D32F:
+ return DXGI_FORMAT_D32_FLOAT;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_D32_FLOAT;
+ }
+}
+
+bool QD3D11Texture::prepareBuild(QSize *adjustedSize)
+{
+ if (tex)
+ release();
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ QRHI_RES_RHI(QRhiD3D11);
+ dxgiFormat = toD3DTextureFormat(m_format, m_flags);
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+ if (sampleDesc.Count > 1) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+ if (isDepth && hasMipMaps) {
+ qWarning("Depth texture cannot have mipmaps");
+ return false;
+ }
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QD3D11Texture::finishBuild()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
+ memset(&srvDesc, 0, sizeof(srvDesc));
+ srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
+ if (isCube) {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
+ srvDesc.TextureCube.MipLevels = mipLevelCount;
+ } else {
+ if (sampleDesc.Count > 1) {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
+ } else {
+ srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+ srvDesc.Texture2D.MipLevels = mipLevelCount;
+ }
+ }
+
+ HRESULT hr = rhiD->dev->CreateShaderResourceView(tex, &srvDesc, &srv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create srv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ nativeHandlesStruct.texture = tex;
+
+ generation += 1;
+ return true;
+}
+
+bool QD3D11Texture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ uint bindFlags = D3D11_BIND_SHADER_RESOURCE;
+ uint miscFlags = isCube ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;
+ if (m_flags.testFlag(RenderTarget)) {
+ if (isDepth)
+ bindFlags |= D3D11_BIND_DEPTH_STENCIL;
+ else
+ bindFlags |= D3D11_BIND_RENDER_TARGET;
+ }
+ if (m_flags.testFlag(UsedWithGenerateMips)) {
+ if (isDepth) {
+ qWarning("Depth texture cannot have mipmaps generated");
+ return false;
+ }
+ bindFlags |= D3D11_BIND_RENDER_TARGET;
+ miscFlags |= D3D11_RESOURCE_MISC_GENERATE_MIPS;
+ }
+ if (m_flags.testFlag(UsedWithLoadStore))
+ bindFlags |= D3D11_BIND_UNORDERED_ACCESS;
+
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = size.width();
+ desc.Height = size.height();
+ desc.MipLevels = mipLevelCount;
+ desc.ArraySize = isCube ? 6 : 1;
+ desc.Format = dxgiFormat;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = bindFlags;
+ desc.MiscFlags = miscFlags;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ if (!finishBuild())
+ return false;
+
+ if (!m_objectName.isEmpty())
+ tex->SetPrivateData(WKPDID_D3DDebugObjectName, m_objectName.size(), m_objectName.constData());
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, sampleDesc.Count));
+
+ owns = true;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QD3D11Texture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiD3D11TextureNativeHandles *h = static_cast<const QRhiD3D11TextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ tex = static_cast<ID3D11Texture2D *>(h->texture);
+
+ if (!finishBuild())
+ return false;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, sampleDesc.Count));
+
+ owns = false;
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QD3D11Texture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level)
+{
+ if (perLevelViews[level])
+ return perLevelViews[level];
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ D3D11_UNORDERED_ACCESS_VIEW_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Format = dxgiFormat;
+ if (isCube) {
+ desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
+ desc.Texture2DArray.MipSlice = level;
+ desc.Texture2DArray.FirstArraySlice = 0;
+ desc.Texture2DArray.ArraySize = 6;
+ } else {
+ desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
+ desc.Texture2D.MipSlice = level;
+ }
+
+ QRHI_RES_RHI(QRhiD3D11);
+ ID3D11UnorderedAccessView *uav = nullptr;
+ HRESULT hr = rhiD->dev->CreateUnorderedAccessView(tex, &desc, &uav);
+ if (FAILED(hr)) {
+ qWarning("Failed to create UAV: %s", qPrintable(comErrorMessage(hr)));
+ return nullptr;
+ }
+
+ perLevelViews[level] = uav;
+ return uav;
+}
+
+QD3D11Sampler::QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QD3D11Sampler::~QD3D11Sampler()
+{
+ release();
+}
+
+void QD3D11Sampler::release()
+{
+ if (!samplerState)
+ return;
+
+ samplerState->Release();
+ samplerState = nullptr;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->unregisterResource(this);
+}
+
+static inline D3D11_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter)
+{
+ if (minFilter == QRhiSampler::Nearest) {
+ if (magFilter == QRhiSampler::Nearest) {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_MAG_MIP_POINT;
+ } else {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
+ }
+ } else {
+ if (magFilter == QRhiSampler::Nearest) {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT;
+ } else {
+ if (mipFilter == QRhiSampler::Linear)
+ return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+ else
+ return D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;
+ }
+ }
+
+ Q_UNREACHABLE();
+ return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+}
+
+static inline D3D11_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return D3D11_TEXTURE_ADDRESS_WRAP;
+ case QRhiSampler::ClampToEdge:
+ return D3D11_TEXTURE_ADDRESS_CLAMP;
+ case QRhiSampler::Border:
+ return D3D11_TEXTURE_ADDRESS_BORDER;
+ case QRhiSampler::Mirror:
+ return D3D11_TEXTURE_ADDRESS_MIRROR;
+ case QRhiSampler::MirrorOnce:
+ return D3D11_TEXTURE_ADDRESS_MIRROR_ONCE;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_TEXTURE_ADDRESS_CLAMP;
+ }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return D3D11_COMPARISON_NEVER;
+ case QRhiSampler::Less:
+ return D3D11_COMPARISON_LESS;
+ case QRhiSampler::Equal:
+ return D3D11_COMPARISON_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return D3D11_COMPARISON_LESS_EQUAL;
+ case QRhiSampler::Greater:
+ return D3D11_COMPARISON_GREATER;
+ case QRhiSampler::NotEqual:
+ return D3D11_COMPARISON_NOT_EQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return D3D11_COMPARISON_GREATER_EQUAL;
+ case QRhiSampler::Always:
+ return D3D11_COMPARISON_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_COMPARISON_NEVER;
+ }
+}
+
+bool QD3D11Sampler::build()
+{
+ if (samplerState)
+ release();
+
+ D3D11_SAMPLER_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode);
+ if (m_compareOp != Never)
+ desc.Filter = D3D11_FILTER(desc.Filter | 0x80);
+ desc.AddressU = toD3DAddressMode(m_addressU);
+ desc.AddressV = toD3DAddressMode(m_addressV);
+ desc.AddressW = toD3DAddressMode(m_addressW);
+ desc.MaxAnisotropy = 1.0f;
+ desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp);
+ desc.MaxLOD = m_mipmapMode == None ? 0.0f : 1000.0f;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateSamplerState(&desc, &samplerState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create sampler state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here
+QD3D11RenderPassDescriptor::QD3D11RenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QD3D11RenderPassDescriptor::~QD3D11RenderPassDescriptor()
+{
+ release();
+}
+
+void QD3D11RenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QD3D11ReferenceRenderTarget::QD3D11ReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QD3D11ReferenceRenderTarget::~QD3D11ReferenceRenderTarget()
+{
+ release();
+}
+
+void QD3D11ReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QD3D11ReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QD3D11ReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QD3D11ReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QD3D11TextureRenderTarget::QD3D11TextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+ ownsRtv[i] = false;
+ rtv[i] = nullptr;
+ }
+}
+
+QD3D11TextureRenderTarget::~QD3D11TextureRenderTarget()
+{
+ release();
+}
+
+void QD3D11TextureRenderTarget::release()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+
+ if (!rtv[0] && !dsv)
+ return;
+
+ if (dsv) {
+ if (ownsDsv)
+ dsv->Release();
+ dsv = nullptr;
+ }
+
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+ if (rtv[i]) {
+ if (ownsRtv[i])
+ rtv[i]->Release();
+ rtv[i] = nullptr;
+ }
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QD3D11TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QD3D11RenderPassDescriptor(m_rhi);
+}
+
+bool QD3D11TextureRenderTarget::build()
+{
+ if (rtv[0] || dsv)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ QRHI_RES_RHI(QRhiD3D11);
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ QRhiTexture *texture = colorAttachments[i].texture();
+ QRhiRenderBuffer *rb = colorAttachments[i].renderBuffer();
+ Q_ASSERT(texture || rb);
+ if (texture) {
+ QD3D11Texture *texD = QRHI_RES(QD3D11Texture, texture);
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
+ if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+ rtvDesc.Texture2DArray.MipSlice = colorAttachments[i].level();
+ rtvDesc.Texture2DArray.FirstArraySlice = colorAttachments[i].layer();
+ rtvDesc.Texture2DArray.ArraySize = 1;
+ } else {
+ if (texD->sampleDesc.Count > 1) {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
+ } else {
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ rtvDesc.Texture2D.MipSlice = colorAttachments[i].level();
+ }
+ }
+ HRESULT hr = rhiD->dev->CreateRenderTargetView(texD->tex, &rtvDesc, &rtv[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ ownsRtv[i] = true;
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = texD->sampleDesc.Count;
+ }
+ } else if (rb) {
+ QD3D11RenderBuffer *rbD = QRHI_RES(QD3D11RenderBuffer, rb);
+ ownsRtv[i] = false;
+ rtv[i] = rbD->rtv;
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->sampleDesc.Count;
+ }
+ }
+ }
+ d.dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ ownsDsv = true;
+ QD3D11Texture *depthTexD = QRHI_RES(QD3D11Texture, m_desc.depthTexture());
+ D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
+ memset(&dsvDesc, 0, sizeof(dsvDesc));
+ dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
+ dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+ : D3D11_DSV_DIMENSION_TEXTURE2D;
+ HRESULT hr = rhiD->dev->CreateDepthStencilView(depthTexD->tex, &dsvDesc, &dsv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create dsv: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = depthTexD->sampleDesc.Count;
+ }
+ } else {
+ ownsDsv = false;
+ QD3D11RenderBuffer *depthRbD = QRHI_RES(QD3D11RenderBuffer, m_desc.depthStencilBuffer());
+ dsv = depthRbD->dsv;
+ if (d.colorAttCount == 0) {
+ d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+ d.sampleCount = depthRbD->sampleDesc.Count;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i)
+ d.rtv[i] = i < d.colorAttCount ? rtv[i] : nullptr;
+
+ d.dsv = dsv;
+ d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QD3D11TextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QD3D11TextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QD3D11TextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QD3D11ShaderResourceBindings::QD3D11ShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QD3D11ShaderResourceBindings::~QD3D11ShaderResourceBindings()
+{
+ release();
+}
+
+void QD3D11ShaderResourceBindings::release()
+{
+ sortedBindings.clear();
+}
+
+bool QD3D11ShaderResourceBindings::build()
+{
+ if (!sortedBindings.isEmpty())
+ release();
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+
+ boundResourceData.resize(sortedBindings.count());
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->updateShaderResourceBindings(this);
+
+ generation += 1;
+ return true;
+}
+
+QD3D11GraphicsPipeline::QD3D11GraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QD3D11GraphicsPipeline::~QD3D11GraphicsPipeline()
+{
+ release();
+}
+
+void QD3D11GraphicsPipeline::release()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+
+ if (!dsState)
+ return;
+
+ dsState->Release();
+ dsState = nullptr;
+
+ if (blendState) {
+ blendState->Release();
+ blendState = nullptr;
+ }
+
+ if (inputLayout) {
+ inputLayout->Release();
+ inputLayout = nullptr;
+ }
+
+ if (rastState) {
+ rastState->Release();
+ rastState = nullptr;
+ }
+
+ if (vs) {
+ vs->Release();
+ vs = nullptr;
+ }
+
+ if (fs) {
+ fs->Release();
+ fs = nullptr;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+static inline D3D11_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return D3D11_CULL_NONE;
+ case QRhiGraphicsPipeline::Front:
+ return D3D11_CULL_FRONT;
+ case QRhiGraphicsPipeline::Back:
+ return D3D11_CULL_BACK;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_CULL_NONE;
+ }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return D3D11_COMPARISON_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return D3D11_COMPARISON_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return D3D11_COMPARISON_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return D3D11_COMPARISON_LESS_EQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return D3D11_COMPARISON_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return D3D11_COMPARISON_NOT_EQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return D3D11_COMPARISON_GREATER_EQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return D3D11_COMPARISON_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_COMPARISON_ALWAYS;
+ }
+}
+
+static inline D3D11_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return D3D11_STENCIL_OP_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return D3D11_STENCIL_OP_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return D3D11_STENCIL_OP_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return D3D11_STENCIL_OP_INCR_SAT;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return D3D11_STENCIL_OP_DECR_SAT;
+ case QRhiGraphicsPipeline::Invert:
+ return D3D11_STENCIL_OP_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return D3D11_STENCIL_OP_INCR;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return D3D11_STENCIL_OP_DECR;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_STENCIL_OP_KEEP;
+ }
+}
+
+static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+ case QRhiVertexInputAttribute::Float3:
+ return DXGI_FORMAT_R32G32B32_FLOAT;
+ case QRhiVertexInputAttribute::Float2:
+ return DXGI_FORMAT_R32G32_FLOAT;
+ case QRhiVertexInputAttribute::Float:
+ return DXGI_FORMAT_R32_FLOAT;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return DXGI_FORMAT_R8G8B8A8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return DXGI_FORMAT_R8G8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte:
+ return DXGI_FORMAT_R8_UNORM;
+ default:
+ Q_UNREACHABLE();
+ return DXGI_FORMAT_R32G32B32A32_FLOAT;
+ }
+}
+
+static inline D3D11_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
+ case QRhiGraphicsPipeline::LineStrip:
+ return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
+ case QRhiGraphicsPipeline::Points:
+ return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ }
+}
+
+static inline uint toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+ uint f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= D3D11_COLOR_WRITE_ENABLE_RED;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= D3D11_COLOR_WRITE_ENABLE_GREEN;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= D3D11_COLOR_WRITE_ENABLE_BLUE;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
+ return f;
+}
+
+static inline D3D11_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return D3D11_BLEND_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return D3D11_BLEND_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return D3D11_BLEND_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return D3D11_BLEND_INV_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return D3D11_BLEND_DEST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return D3D11_BLEND_INV_DEST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return D3D11_BLEND_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return D3D11_BLEND_INV_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return D3D11_BLEND_DEST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return D3D11_BLEND_INV_DEST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return D3D11_BLEND_BLEND_FACTOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return D3D11_BLEND_INV_BLEND_FACTOR;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return D3D11_BLEND_SRC_ALPHA_SAT;
+ case QRhiGraphicsPipeline::Src1Color:
+ return D3D11_BLEND_SRC1_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return D3D11_BLEND_INV_SRC1_COLOR;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return D3D11_BLEND_SRC1_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return D3D11_BLEND_INV_SRC1_ALPHA;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_BLEND_ZERO;
+ }
+}
+
+static inline D3D11_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return D3D11_BLEND_OP_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return D3D11_BLEND_OP_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return D3D11_BLEND_OP_REV_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return D3D11_BLEND_OP_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return D3D11_BLEND_OP_MAX;
+ default:
+ Q_UNREACHABLE();
+ return D3D11_BLEND_OP_ADD;
+ }
+}
+
+static pD3DCompile resolveD3DCompile()
+{
+ for (const wchar_t *libraryName : {L"D3DCompiler_47", L"D3DCompiler_43"}) {
+ QSystemLibrary library(libraryName);
+ if (library.load()) {
+ if (auto symbol = library.resolve("D3DCompile"))
+ return reinterpret_cast<pD3DCompile>(symbol);
+ }
+ }
+ return nullptr;
+}
+
+static QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, QString *error)
+{
+ QShaderCode dxbc = shader.shader({ QShader::DxbcShader, 50, shaderVariant });
+ if (!dxbc.shader().isEmpty())
+ return dxbc.shader();
+
+ QShaderCode hlslSource = shader.shader({ QShader::HlslShader, 50, shaderVariant });
+ if (hlslSource.shader().isEmpty()) {
+ qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader;
+ return QByteArray();
+ }
+
+ const char *target;
+ switch (shader.stage()) {
+ case QShader::VertexStage:
+ target = "vs_5_0";
+ break;
+ case QShader::TessellationControlStage:
+ target = "hs_5_0";
+ break;
+ case QShader::TessellationEvaluationStage:
+ target = "ds_5_0";
+ break;
+ case QShader::GeometryStage:
+ target = "gs_5_0";
+ break;
+ case QShader::FragmentStage:
+ target = "ps_5_0";
+ break;
+ case QShader::ComputeStage:
+ target = "cs_5_0";
+ break;
+ default:
+ Q_UNREACHABLE();
+ return QByteArray();
+ }
+
+ static const pD3DCompile d3dCompile = resolveD3DCompile();
+ if (d3dCompile == nullptr) {
+ qWarning("Unable to resolve function D3DCompile()");
+ return QByteArray();
+ }
+
+ ID3DBlob *bytecode = nullptr;
+ ID3DBlob *errors = nullptr;
+ HRESULT hr = d3dCompile(hlslSource.shader().constData(), hlslSource.shader().size(),
+ nullptr, nullptr, nullptr,
+ hlslSource.entryPoint().constData(), target, 0, 0, &bytecode, &errors);
+ if (FAILED(hr) || !bytecode) {
+ qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
+ if (errors) {
+ *error = QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()),
+ errors->GetBufferSize());
+ errors->Release();
+ }
+ return QByteArray();
+ }
+
+ QByteArray result;
+ result.resize(bytecode->GetBufferSize());
+ memcpy(result.data(), bytecode->GetBufferPointer(), result.size());
+ bytecode->Release();
+ return result;
+}
+
+bool QD3D11GraphicsPipeline::build()
+{
+ if (dsState)
+ release();
+
+ QRHI_RES_RHI(QRhiD3D11);
+
+ D3D11_RASTERIZER_DESC rastDesc;
+ memset(&rastDesc, 0, sizeof(rastDesc));
+ rastDesc.FillMode = D3D11_FILL_SOLID;
+ rastDesc.CullMode = toD3DCullMode(m_cullMode);
+ rastDesc.FrontCounterClockwise = m_frontFace == CCW;
+ rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
+ rastDesc.MultisampleEnable = rhiD->effectiveSampleCount(m_sampleCount).Count > 1;
+ HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rasterizer state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_DEPTH_STENCIL_DESC dsDesc;
+ memset(&dsDesc, 0, sizeof(dsDesc));
+ dsDesc.DepthEnable = m_depthTest;
+ dsDesc.DepthWriteMask = m_depthWrite ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
+ dsDesc.DepthFunc = toD3DCompareOp(m_depthOp);
+ dsDesc.StencilEnable = m_stencilTest;
+ if (m_stencilTest) {
+ dsDesc.StencilReadMask = m_stencilReadMask;
+ dsDesc.StencilWriteMask = m_stencilWriteMask;
+ dsDesc.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
+ dsDesc.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
+ dsDesc.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
+ dsDesc.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
+ dsDesc.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
+ dsDesc.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
+ }
+ hr = rhiD->dev->CreateDepthStencilState(&dsDesc, &dsState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create depth-stencil state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_BLEND_DESC blendDesc;
+ memset(&blendDesc, 0, sizeof(blendDesc));
+ blendDesc.IndependentBlendEnable = m_targetBlends.count() > 1;
+ for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+ D3D11_RENDER_TARGET_BLEND_DESC blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.BlendEnable = b.enable;
+ blend.SrcBlend = toD3DBlendFactor(b.srcColor);
+ blend.DestBlend = toD3DBlendFactor(b.dstColor);
+ blend.BlendOp = toD3DBlendOp(b.opColor);
+ blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha);
+ blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha);
+ blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
+ blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
+ blendDesc.RenderTarget[i] = blend;
+ }
+ if (m_targetBlends.isEmpty()) {
+ D3D11_RENDER_TARGET_BLEND_DESC blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+ blendDesc.RenderTarget[0] = blend;
+ }
+ hr = rhiD->dev->CreateBlendState(&blendDesc, &blendState);
+ if (FAILED(hr)) {
+ qWarning("Failed to create blend state: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ QByteArray vsByteCode;
+ for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ QString error;
+ QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), shaderStage.shaderVariant(), &error);
+ if (bytecode.isEmpty()) {
+ qWarning("HLSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ switch (shaderStage.type()) {
+ case QRhiShaderStage::Vertex:
+ hr = rhiD->dev->CreateVertexShader(bytecode.constData(), bytecode.size(), nullptr, &vs);
+ if (FAILED(hr)) {
+ qWarning("Failed to create vertex shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ vsByteCode = bytecode;
+ break;
+ case QRhiShaderStage::Fragment:
+ hr = rhiD->dev->CreatePixelShader(bytecode.constData(), bytecode.size(), nullptr, &fs);
+ if (FAILED(hr)) {
+ qWarning("Failed to create pixel shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ d3dTopology = toD3DTopology(m_topology);
+
+ if (!vsByteCode.isEmpty()) {
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ QVarLengthArray<D3D11_INPUT_ELEMENT_DESC, 4> inputDescs;
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ D3D11_INPUT_ELEMENT_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ // the output from SPIRV-Cross uses TEXCOORD<location> as the semantic
+ desc.SemanticName = "TEXCOORD";
+ desc.SemanticIndex = attribute.location();
+ desc.Format = toD3DAttributeFormat(attribute.format());
+ desc.InputSlot = attribute.binding();
+ desc.AlignedByteOffset = attribute.offset();
+ const QRhiVertexInputBinding &binding(bindings[attribute.binding()]);
+ if (binding.classification() == QRhiVertexInputBinding::PerInstance) {
+ desc.InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
+ desc.InstanceDataStepRate = binding.instanceStepRate();
+ } else {
+ desc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ }
+ inputDescs.append(desc);
+ }
+ hr = rhiD->dev->CreateInputLayout(inputDescs.constData(), inputDescs.count(), vsByteCode, vsByteCode.size(), &inputLayout);
+ if (FAILED(hr)) {
+ qWarning("Failed to create input layout: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ }
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QD3D11ComputePipeline::QD3D11ComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi)
+{
+}
+
+QD3D11ComputePipeline::~QD3D11ComputePipeline()
+{
+ release();
+}
+
+void QD3D11ComputePipeline::release()
+{
+ QRHI_RES_RHI(QRhiD3D11);
+
+ if (!cs)
+ return;
+
+ cs->Release();
+ cs = nullptr;
+
+ rhiD->unregisterResource(this);
+}
+
+bool QD3D11ComputePipeline::build()
+{
+ if (cs)
+ release();
+
+ QRHI_RES_RHI(QRhiD3D11);
+
+ QString error;
+ QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(), m_shaderStage.shaderVariant(), &error);
+ if (bytecode.isEmpty()) {
+ qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+
+ HRESULT hr = rhiD->dev->CreateComputeShader(bytecode.constData(), bytecode.size(), nullptr, &cs);
+ if (FAILED(hr)) {
+ qWarning("Failed to create compute shader: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QD3D11CommandBuffer::QD3D11CommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QD3D11CommandBuffer::~QD3D11CommandBuffer()
+{
+ release();
+}
+
+void QD3D11CommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ tex[i] = nullptr;
+ rtv[i] = nullptr;
+ msaaTex[i] = nullptr;
+ msaaRtv[i] = nullptr;
+ timestampActive[i] = false;
+ timestampDisjointQuery[i] = nullptr;
+ timestampQuery[2 * i] = nullptr;
+ timestampQuery[2 * i + 1] = nullptr;
+ }
+}
+
+QD3D11SwapChain::~QD3D11SwapChain()
+{
+ release();
+}
+
+void QD3D11SwapChain::releaseBuffers()
+{
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (rtv[i]) {
+ rtv[i]->Release();
+ rtv[i] = nullptr;
+ }
+ if (tex[i]) {
+ tex[i]->Release();
+ tex[i] = nullptr;
+ }
+ if (msaaRtv[i]) {
+ msaaRtv[i]->Release();
+ msaaRtv[i] = nullptr;
+ }
+ if (msaaTex[i]) {
+ msaaTex[i]->Release();
+ msaaTex[i] = nullptr;
+ }
+ }
+}
+
+void QD3D11SwapChain::release()
+{
+ if (!swapChain)
+ return;
+
+ releaseBuffers();
+
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (timestampDisjointQuery[i]) {
+ timestampDisjointQuery[i]->Release();
+ timestampDisjointQuery[i] = nullptr;
+ }
+ for (int j = 0; j < 2; ++j) {
+ const int idx = BUFFER_COUNT * i + j;
+ if (timestampQuery[idx]) {
+ timestampQuery[idx]->Release();
+ timestampQuery[idx] = nullptr;
+ }
+ }
+ }
+
+ swapChain->Release();
+ swapChain = nullptr;
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ QRHI_RES_RHI(QRhiD3D11);
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QD3D11SwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QD3D11SwapChain::surfacePixelSize()
+{
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
+}
+
+QRhiRenderPassDescriptor *QD3D11SwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QD3D11RenderPassDescriptor(m_rhi);
+}
+
+bool QD3D11SwapChain::newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+ ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const
+{
+ D3D11_TEXTURE2D_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = size.width();
+ desc.Height = size.height();
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = format;
+ desc.SampleDesc = sampleDesc;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, tex);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color buffer texture: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = format;
+ rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(*tex, &rtvDesc, rtv);
+ if (FAILED(hr)) {
+ qWarning("Failed to create color buffer rtv: %s", qPrintable(comErrorMessage(hr)));
+ (*tex)->Release();
+ *tex = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool QD3D11SwapChain::buildOrResize()
+{
+ // Can be called multiple times due to window resizes - that is not the
+ // same as a simple release+build (as with other resources). Just need to
+ // resize the buffers then.
+
+ const bool needsRegistration = !window || window != m_window;
+
+ // except if the window actually changes
+ if (window && window != m_window)
+ release();
+
+ window = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ if (pixelSize.isEmpty())
+ return false;
+
+ colorFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
+ const DXGI_FORMAT srgbAdjustedFormat = m_flags.testFlag(sRGB) ?
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+
+ const UINT swapChainFlags = 0;
+
+ QRHI_RES_RHI(QRhiD3D11);
+ if (!swapChain) {
+ HWND hwnd = reinterpret_cast<HWND>(window->winId());
+ sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+
+ // We use FLIP_DISCARD which implies a buffer count of 2 (as opposed to the
+ // old DISCARD with back buffer count == 1). This makes no difference for
+ // the rest of the stuff except that automatic MSAA is unsupported and
+ // needs to be implemented via a custom multisample render target and an
+ // explicit resolve.
+
+ HRESULT hr;
+ if (rhiD->hasDxgi2) {
+ DXGI_SWAP_CHAIN_DESC1 desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.Width = pixelSize.width();
+ desc.Height = pixelSize.height();
+ desc.Format = colorFormat;
+ desc.SampleDesc.Count = 1;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.BufferCount = BUFFER_COUNT;
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ if (m_flags.testFlag(SurfaceHasPreMulAlpha))
+ desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+ else if (m_flags.testFlag(SurfaceHasNonPreMulAlpha))
+ desc.AlphaMode = DXGI_ALPHA_MODE_STRAIGHT;
+ desc.Flags = swapChainFlags;
+
+ IDXGISwapChain1 *sc1;
+ hr = static_cast<IDXGIFactory2 *>(rhiD->dxgiFactory)->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc,
+ nullptr, nullptr, &sc1);
+ if (SUCCEEDED(hr))
+ swapChain = sc1;
+ } else {
+ // Windows 7
+ DXGI_SWAP_CHAIN_DESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.BufferDesc.Width = pixelSize.width();
+ desc.BufferDesc.Height = pixelSize.height();
+ desc.BufferDesc.RefreshRate.Numerator = 60;
+ desc.BufferDesc.RefreshRate.Denominator = 1;
+ desc.BufferDesc.Format = colorFormat;
+ desc.SampleDesc.Count = 1;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.BufferCount = BUFFER_COUNT;
+ desc.OutputWindow = hwnd;
+ desc.Windowed = true;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ desc.Flags = swapChainFlags;
+
+ hr = rhiD->dxgiFactory->CreateSwapChain(rhiD->dev, &desc, &swapChain);
+ }
+ if (FAILED(hr)) {
+ qWarning("Failed to create D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ } else {
+ releaseBuffers();
+ HRESULT hr = swapChain->ResizeBuffers(2, pixelSize.width(), pixelSize.height(), colorFormat, swapChainFlags);
+ if (FAILED(hr)) {
+ qWarning("Failed to resize D3D11 swapchain: %s", qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ }
+
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ HRESULT hr = swapChain->GetBuffer(0, IID_ID3D11Texture2D, reinterpret_cast<void **>(&tex[i]));
+ if (FAILED(hr)) {
+ qWarning("Failed to query swapchain buffer %d: %s", i, qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
+ memset(&rtvDesc, 0, sizeof(rtvDesc));
+ rtvDesc.Format = srgbAdjustedFormat;
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+ hr = rhiD->dev->CreateRenderTargetView(tex[i], &rtvDesc, &rtv[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv for swapchain buffer %d: %s", i, qPrintable(comErrorMessage(hr)));
+ return false;
+ }
+ if (sampleDesc.Count > 1) {
+ if (!newColorBuffer(pixelSize, srgbAdjustedFormat, sampleDesc, &msaaTex[i], &msaaRtv[i]))
+ return false;
+ }
+ }
+
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ currentFrameSlot = 0;
+ frameCount = 0;
+ ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr;
+ swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1;
+
+ QD3D11ReferenceRenderTarget *rtD = QRHI_RES(QD3D11ReferenceRenderTarget, &rt);
+ rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+ rtD->d.pixelSize = pixelSize;
+ rtD->d.dpr = window->devicePixelRatio();
+ rtD->d.sampleCount = sampleDesc.Count;
+ rtD->d.colorAttCount = 1;
+ rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, BUFFER_COUNT, sampleDesc.Count > 1 ? BUFFER_COUNT : 0, sampleDesc.Count));
+ if (rhiP) {
+ D3D11_QUERY_DESC queryDesc;
+ memset(&queryDesc, 0, sizeof(queryDesc));
+ for (int i = 0; i < BUFFER_COUNT; ++i) {
+ if (!timestampDisjointQuery[i]) {
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
+ HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &timestampDisjointQuery[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp disjoint query: %s", qPrintable(comErrorMessage(hr)));
+ break;
+ }
+ }
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP;
+ for (int j = 0; j < 2; ++j) {
+ const int idx = BUFFER_COUNT * i + j; // one pair per buffer (frame)
+ if (!timestampQuery[idx]) {
+ HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &timestampQuery[idx]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp query: %s", qPrintable(comErrorMessage(hr)));
+ break;
+ }
+ }
+ }
+ }
+ // timestamp queries are optional so we can go on even if they failed
+ }
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h
new file mode 100644
index 0000000000..3e2e492d9c
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11_p.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHID3D11_H
+#define QRHID3D11_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+// no d3d includes here, to prevent precompiled header mess (due to this being
+// a public header)
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiD3D11InitParams : public QRhiInitParams
+{
+ bool enableDebugLayer = false;
+};
+
+struct Q_GUI_EXPORT QRhiD3D11NativeHandles : public QRhiNativeHandles
+{
+ void *dev = nullptr;
+ void *context = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiD3D11TextureNativeHandles : public QRhiNativeHandles
+{
+ void *texture = nullptr; // ID3D11Texture2D*
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhid3d11_p_p.h b/src/gui/rhi/qrhid3d11_p_p.h
new file mode 100644
index 0000000000..34c9ff70f8
--- /dev/null
+++ b/src/gui/rhi/qrhid3d11_p_p.h
@@ -0,0 +1,693 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHID3D11_P_H
+#define QRHID3D11_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhid3d11_p.h"
+#include "qrhi_p_p.h"
+#include "qshaderdescription_p.h"
+#include <QWindow>
+
+#include <d3d11_1.h>
+#include <dxgi1_3.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QD3D11Buffer : public QRhiBuffer
+{
+ QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QD3D11Buffer();
+ void release() override;
+ bool build() override;
+
+ ID3D11UnorderedAccessView *unorderedAccessView();
+
+ ID3D11Buffer *buffer = nullptr;
+ QByteArray dynBuf;
+ bool hasPendingDynamicUpdates = false;
+ ID3D11UnorderedAccessView *uav = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11RenderBuffer : public QRhiRenderBuffer
+{
+ QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QD3D11RenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ ID3D11Texture2D *tex = nullptr;
+ ID3D11DepthStencilView *dsv = nullptr;
+ ID3D11RenderTargetView *rtv = nullptr;
+ DXGI_FORMAT dxgiFormat;
+ DXGI_SAMPLE_DESC sampleDesc;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11Texture : public QRhiTexture
+{
+ QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QD3D11Texture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+ bool finishBuild();
+ ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level);
+
+ ID3D11Texture2D *tex = nullptr;
+ bool owns = true;
+ ID3D11ShaderResourceView *srv = nullptr;
+ DXGI_FORMAT dxgiFormat;
+ uint mipLevelCount = 0;
+ DXGI_SAMPLE_DESC sampleDesc;
+ QRhiD3D11TextureNativeHandles nativeHandlesStruct;
+ ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_LEVELS];
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11Sampler : public QRhiSampler
+{
+ QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QD3D11Sampler();
+ void release() override;
+ bool build() override;
+
+ ID3D11SamplerState *samplerState = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QD3D11RenderPassDescriptor(QRhiImplementation *rhi);
+ ~QD3D11RenderPassDescriptor();
+ void release() override;
+};
+
+struct QD3D11RenderTargetData
+{
+ QD3D11RenderTargetData(QRhiImplementation *)
+ {
+ for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
+ rtv[i] = nullptr;
+ }
+
+ QD3D11RenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+ ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS];
+ ID3D11DepthStencilView *dsv = nullptr;
+};
+
+struct QD3D11ReferenceRenderTarget : public QRhiRenderTarget
+{
+ QD3D11ReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QD3D11ReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QD3D11RenderTargetData d;
+};
+
+struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QD3D11TextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QD3D11RenderTargetData d;
+ bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+ ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+ bool ownsDsv = false;
+ ID3D11DepthStencilView *dsv = nullptr;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QD3D11ShaderResourceBindings(QRhiImplementation *rhi);
+ ~QD3D11ShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ uint generation = 0;
+
+ // Keep track of the generation number of each referenced QRhi* to be able
+ // to detect that the batched bindings are out of date.
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundStorageImageData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundStorageBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ BoundStorageImageData simage;
+ BoundStorageBufferData sbuf;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData;
+
+ QRhiBatchedBindings<ID3D11Buffer *> vsubufs;
+ QRhiBatchedBindings<UINT> vsubufoffsets;
+ QRhiBatchedBindings<UINT> vsubufsizes;
+
+ QRhiBatchedBindings<ID3D11Buffer *> fsubufs;
+ QRhiBatchedBindings<UINT> fsubufoffsets;
+ QRhiBatchedBindings<UINT> fsubufsizes;
+
+ QRhiBatchedBindings<ID3D11Buffer *> csubufs;
+ QRhiBatchedBindings<UINT> csubufoffsets;
+ QRhiBatchedBindings<UINT> csubufsizes;
+
+ QRhiBatchedBindings<ID3D11SamplerState *> vssamplers;
+ QRhiBatchedBindings<ID3D11ShaderResourceView *> vsshaderresources;
+
+ QRhiBatchedBindings<ID3D11SamplerState *> fssamplers;
+ QRhiBatchedBindings<ID3D11ShaderResourceView *> fsshaderresources;
+
+ QRhiBatchedBindings<ID3D11SamplerState *> cssamplers;
+ QRhiBatchedBindings<ID3D11ShaderResourceView *> csshaderresources;
+
+ QRhiBatchedBindings<ID3D11UnorderedAccessView *> csUAVs;
+
+ friend class QRhiD3D11;
+};
+
+Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_MOVABLE_TYPE);
+
+struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QD3D11GraphicsPipeline(QRhiImplementation *rhi);
+ ~QD3D11GraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ ID3D11DepthStencilState *dsState = nullptr;
+ ID3D11BlendState *blendState = nullptr;
+ ID3D11VertexShader *vs = nullptr;
+ ID3D11PixelShader *fs = nullptr;
+ ID3D11InputLayout *inputLayout = nullptr;
+ D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ ID3D11RasterizerState *rastState = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11ComputePipeline : public QRhiComputePipeline
+{
+ QD3D11ComputePipeline(QRhiImplementation *rhi);
+ ~QD3D11ComputePipeline();
+ void release() override;
+ bool build() override;
+
+ ID3D11ComputeShader *cs = nullptr;
+ uint generation = 0;
+ friend class QRhiD3D11;
+};
+
+struct QD3D11SwapChain;
+
+struct QD3D11CommandBuffer : public QRhiCommandBuffer
+{
+ QD3D11CommandBuffer(QRhiImplementation *rhi);
+ ~QD3D11CommandBuffer();
+ void release() override;
+
+ struct Command {
+ enum Cmd {
+ ResetShaderResources,
+ SetRenderTarget,
+ Clear,
+ Viewport,
+ Scissor,
+ BindVertexBuffers,
+ BindIndexBuffer,
+ BindGraphicsPipeline,
+ BindShaderResources,
+ StencilRef,
+ BlendConstants,
+ Draw,
+ DrawIndexed,
+ UpdateSubRes,
+ CopySubRes,
+ ResolveSubRes,
+ GenMip,
+ DebugMarkBegin,
+ DebugMarkEnd,
+ DebugMarkMsg,
+ BindComputePipeline,
+ Dispatch
+ };
+ enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 };
+ Cmd cmd;
+
+ static const int MAX_UBUF_BINDINGS = 32; // should be D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT but 128 is a waste of space for our purposes
+
+ // QRhi*/QD3D11* references should be kept at minimum (so no
+ // QRhiTexture/Buffer/etc. pointers).
+ union {
+ struct {
+ QRhiRenderTarget *rt;
+ } setRenderTarget;
+ struct {
+ QRhiRenderTarget *rt;
+ int mask;
+ float c[4];
+ float d;
+ quint32 s;
+ } clear;
+ struct {
+ float x, y, w, h;
+ float d0, d1;
+ } viewport;
+ struct {
+ int x, y, w, h;
+ } scissor;
+ struct {
+ int startSlot;
+ int slotCount;
+ ID3D11Buffer *buffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ UINT offsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ UINT strides[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ } bindVertexBuffers;
+ struct {
+ ID3D11Buffer *buffer;
+ quint32 offset;
+ DXGI_FORMAT format;
+ } bindIndexBuffer;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ } bindGraphicsPipeline;
+ struct {
+ QD3D11ShaderResourceBindings *srb;
+ bool offsetOnlyChange;
+ int dynamicOffsetCount;
+ uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offsetInConstants
+ } bindShaderResources;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 ref;
+ } stencilRef;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ float c[4];
+ } blendConstants;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 vertexCount;
+ quint32 instanceCount;
+ quint32 firstVertex;
+ quint32 firstInstance;
+ } draw;
+ struct {
+ QD3D11GraphicsPipeline *ps;
+ quint32 indexCount;
+ quint32 instanceCount;
+ quint32 firstIndex;
+ qint32 vertexOffset;
+ quint32 firstInstance;
+ } drawIndexed;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ bool hasDstBox;
+ D3D11_BOX dstBox;
+ const void *src; // must come from retain*()
+ UINT srcRowPitch;
+ } updateSubRes;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ UINT dstX;
+ UINT dstY;
+ ID3D11Resource *src;
+ UINT srcSubRes;
+ bool hasSrcBox;
+ D3D11_BOX srcBox;
+ } copySubRes;
+ struct {
+ ID3D11Resource *dst;
+ UINT dstSubRes;
+ ID3D11Resource *src;
+ UINT srcSubRes;
+ DXGI_FORMAT format;
+ } resolveSubRes;
+ struct {
+ ID3D11ShaderResourceView *srv;
+ } genMip;
+ struct {
+ char s[64];
+ } debugMark;
+ struct {
+ QD3D11ComputePipeline *ps;
+ } bindComputePipeline;
+ struct {
+ UINT x;
+ UINT y;
+ UINT z;
+ } dispatch;
+ } args;
+ };
+
+ enum PassType {
+ NoPass,
+ RenderPass,
+ ComputePass
+ };
+
+ QVector<Command> commands;
+ PassType recordingPass;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentGraphicsPipeline;
+ QRhiComputePipeline *currentComputePipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentGraphicsSrb;
+ QRhiShaderResourceBindings *currentComputeSrb;
+ uint currentSrbGeneration;
+ ID3D11Buffer *currentIndexBuffer;
+ quint32 currentIndexOffset;
+ DXGI_FORMAT currentIndexFormat;
+ ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+
+ QVector<QByteArray> dataRetainPool;
+ QVector<QImage> imageRetainPool;
+
+ // relies heavily on implicit sharing (no copies of the actual data will be made)
+ const uchar *retainData(const QByteArray &data) {
+ dataRetainPool.append(data);
+ return reinterpret_cast<const uchar *>(dataRetainPool.constLast().constData());
+ }
+ const uchar *retainImage(const QImage &image) {
+ imageRetainPool.append(image);
+ return imageRetainPool.constLast().constBits();
+ }
+ void resetCommands() {
+ commands.clear();
+ dataRetainPool.clear();
+ imageRetainPool.clear();
+ }
+ void resetState() {
+ resetCommands();
+ recordingPass = NoPass;
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+ void resetCachedState() {
+ currentGraphicsPipeline = nullptr;
+ currentComputePipeline = nullptr;
+ currentPipelineGeneration = 0;
+ resetCachedShaderResourceState();
+ }
+ void resetCachedShaderResourceState() {
+ currentGraphicsSrb = nullptr;
+ currentComputeSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentIndexBuffer = nullptr;
+ currentIndexOffset = 0;
+ currentIndexFormat = DXGI_FORMAT_R16_UINT;
+ memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
+ memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
+ }
+};
+
+Q_DECLARE_TYPEINFO(QD3D11CommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QD3D11SwapChain : public QRhiSwapChain
+{
+ QD3D11SwapChain(QRhiImplementation *rhi);
+ ~QD3D11SwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ void releaseBuffers();
+ bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+ ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ QD3D11ReferenceRenderTarget rt;
+ QD3D11CommandBuffer cb;
+ DXGI_FORMAT colorFormat;
+ IDXGISwapChain *swapChain = nullptr;
+ static const int BUFFER_COUNT = 2;
+ ID3D11Texture2D *tex[BUFFER_COUNT];
+ ID3D11RenderTargetView *rtv[BUFFER_COUNT];
+ ID3D11Texture2D *msaaTex[BUFFER_COUNT];
+ ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
+ DXGI_SAMPLE_DESC sampleDesc;
+ int currentFrameSlot = 0;
+ int frameCount = 0;
+ QD3D11RenderBuffer *ds = nullptr;
+ bool timestampActive[BUFFER_COUNT];
+ ID3D11Query *timestampDisjointQuery[BUFFER_COUNT];
+ ID3D11Query *timestampQuery[BUFFER_COUNT * 2];
+ UINT swapInterval = 1;
+};
+
+class QRhiD3D11 : public QRhiImplementation
+{
+public:
+ QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiComputePipeline *createComputePipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+ void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+ void makeThreadLocalNativeContextCurrent() override;
+
+ void flushCommandBuffer();
+ void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD);
+ void executeBufferHostWritesForCurrentFrame(QD3D11Buffer *bufD);
+ void bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+ const uint *dynOfsPairs, int dynOfsPairCount,
+ bool offsetOnlyChange);
+ void resetShaderResources();
+ void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr);
+ DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
+ void finishActiveReadbacks();
+ void reportLiveObjects(ID3D11Device *device);
+
+ bool debugLayer = false;
+ bool importedDevice = false;
+ ID3D11Device *dev = nullptr;
+ ID3D11DeviceContext1 *context = nullptr;
+ D3D_FEATURE_LEVEL featureLevel;
+ ID3DUserDefinedAnnotation *annotations = nullptr;
+ IDXGIFactory1 *dxgiFactory = nullptr;
+ bool hasDxgi2 = false;
+ QRhiD3D11NativeHandles nativeHandlesStruct;
+
+ struct {
+ int vsHighestActiveVertexBufferBinding = -1;
+ bool vsHasIndexBufferBound = false;
+ int vsHighestActiveSrvBinding = -1;
+ int fsHighestActiveSrvBinding = -1;
+ int csHighestActiveSrvBinding = -1;
+ int csHighestActiveUavBinding = -1;
+ QD3D11SwapChain *currentSwapChain = nullptr;
+ } contextState;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QD3D11CommandBuffer cbWrapper;
+ } ofr;
+
+ struct ActiveReadback {
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ ID3D11Texture2D *stagingTex;
+ quint32 bufSize;
+ quint32 bpl;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+};
+
+Q_DECLARE_TYPEINFO(QRhiD3D11::ActiveReadback, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
new file mode 100644
index 0000000000..22cb030c27
--- /dev/null
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -0,0 +1,3161 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhigles2_p_p.h"
+#include <QWindow>
+#include <QOffscreenSurface>
+#include <QOpenGLContext>
+#include <QtGui/private/qopenglextensions_p.h>
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ OpenGL backend. Binding vertex attribute locations and decomposing uniform
+ buffers into uniforms are handled transparently to the application via the
+ reflection data (QShaderDescription). Real uniform buffers are never used,
+ regardless of the GLSL version. Textures and buffers feature no special logic,
+ it's all just glTexSubImage2D and glBufferSubData (with "dynamic" buffers set
+ to GL_DYNAMIC_DRAW). The swapchain and the associated renderbuffer for
+ depth-stencil will be dummies since we have no control over the underlying
+ buffers here. While we try to keep this backend clean GLES 2.0, some GL(ES)
+ 3.0 features like multisample renderbuffers and blits are used when available.
+*/
+
+/*!
+ \class QRhiGles2InitParams
+ \inmodule QtRhi
+ \brief OpenGL specific initialization parameters.
+
+ An OpenGL-based QRhi needs an already created QOffscreenSurface at minimum.
+ Additionally, while optional, it is recommended that the QWindow the first
+ QRhiSwapChain will target is passed in as well.
+
+ \badcode
+ QOffscreenSurface *fallbackSurface = QRhiGles2InitParams::newFallbackSurface();
+ QRhiGles2InitParams params;
+ params.fallbackSurface = fallbackSurface;
+ params.window = window;
+ rhi = QRhi::create(QRhi::OpenGLES2, &params);
+ \endcode
+
+ By default QRhi creates a QOpenGLContext on its own. This approach works
+ well in most cases, included threaded scenarios, where there is a dedicated
+ QRhi for each rendering thread. As there will be a QOpenGLContext for each
+ QRhi, the OpenGL context requirements (a context can only be current on one
+ thread) are satisfied. The implicitly created context is destroyed
+ automatically together with the QRhi.
+
+ The QSurfaceFormat for the context is specified in \l format. The
+ constructor sets this to QSurfaceFormat::defaultFormat() so applications
+ that use QSurfaceFormat::setDefaultFormat() do not need to set the format
+ again.
+
+ \note The depth and stencil buffer sizes are set automatically to 24 and 8
+ when no size was explicitly set for these buffers in \l format. As there
+ are possible adjustments to \l format, applications can use
+ adjustedFormat() to query the effective format that is passed to
+ QOpenGLContext::setFormat() internally.
+
+ A QOffscreenSurface has to be specified in \l fallbackSurface. In order to
+ prevent mistakes in threaded situations, this is never created
+ automatically by the QRhi since, like QWindow, QOffscreenSurface can only
+ be created on the gui/main thread.
+
+ As a convenience, applications can use newFallbackSurface() which creates
+ and returns a QOffscreenSurface that is compatible with the QOpenGLContext
+ that is going to be created by the QRhi afterwards. Note that the ownership
+ of the returned QOffscreenSurface is transferred to the caller and the QRhi
+ will not destroy it.
+
+ \note QRhiSwapChain can only target QWindow instances that have their
+ surface type set to QSurface::OpenGLSurface.
+
+ \note \l window is optional. It is recommended to specify it whenever
+ possible, in order to avoid problems on multi-adapter and multi-screen
+ systems. When \l window is not set, the very first
+ QOpenGLContext::makeCurrent() happens with \l fallbackSurface which may be
+ an invisible window on some platforms (for example, Windows) and that may
+ trigger unexpected problems in some cases.
+
+ \section2 Working with existing OpenGL contexts
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same OpenGL context. This can be achieved
+ by passing a pointer to a QRhiGles2NativeHandles to QRhi::create(). The
+ \l{QRhiGles2NativeHandles::context}{context} must be set to a non-null
+ value.
+
+ An alternative approach is to create a QOpenGLContext that
+ \l{QOpenGLContext::setShareContext()}{shares resources} with the other
+ engine's context and passing in that context via QRhiGles2NativeHandles.
+
+ The QRhi does not take ownership of the QOpenGLContext passed in via
+ QRhiGles2NativeHandles.
+ */
+
+/*!
+ \class QRhiGles2NativeHandles
+ \inmodule QtRhi
+ \brief Holds the OpenGL context used by the QRhi.
+ */
+
+/*!
+ \class QRhiGles2TextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the OpenGL texture object that is backing a QRhiTexture instance.
+ */
+
+#ifndef GL_BGRA
+#define GL_BGRA 0x80E1
+#endif
+
+#ifndef GL_R8
+#define GL_R8 0x8229
+#endif
+
+#ifndef GL_R16
+#define GL_R16 0x822A
+#endif
+
+#ifndef GL_RED
+#define GL_RED 0x1903
+#endif
+
+#ifndef GL_RGBA8
+#define GL_RGBA8 0x8058
+#endif
+
+#ifndef GL_RGBA32F
+#define GL_RGBA32F 0x8814
+#endif
+
+#ifndef GL_RGBA16F
+#define GL_RGBA16F 0x881A
+#endif
+
+#ifndef GL_HALF_FLOAT
+#define GL_HALF_FLOAT 0x140B
+#endif
+
+#ifndef GL_DEPTH_COMPONENT16
+#define GL_DEPTH_COMPONENT16 0x81A5
+#endif
+
+#ifndef GL_DEPTH_COMPONENT24
+#define GL_DEPTH_COMPONENT24 0x81A6
+#endif
+
+#ifndef GL_DEPTH_COMPONENT32F
+#define GL_DEPTH_COMPONENT32F 0x8CAC
+#endif
+
+#ifndef GL_STENCIL_INDEX
+#define GL_STENCIL_INDEX 0x1901
+#endif
+
+#ifndef GL_STENCIL_INDEX8
+#define GL_STENCIL_INDEX8 0x8D48
+#endif
+
+#ifndef GL_DEPTH24_STENCIL8
+#define GL_DEPTH24_STENCIL8 0x88F0
+#endif
+
+#ifndef GL_DEPTH_STENCIL_ATTACHMENT
+#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
+#endif
+
+#ifndef GL_DEPTH_STENCIL
+#define GL_DEPTH_STENCIL 0x84F9
+#endif
+
+#ifndef GL_PRIMITIVE_RESTART_FIXED_INDEX
+#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
+#endif
+
+#ifndef GL_FRAMEBUFFER_SRGB
+#define GL_FRAMEBUFFER_SRGB 0x8DB9
+#endif
+
+#ifndef GL_READ_FRAMEBUFFER
+#define GL_READ_FRAMEBUFFER 0x8CA8
+#endif
+
+#ifndef GL_DRAW_FRAMEBUFFER
+#define GL_DRAW_FRAMEBUFFER 0x8CA9
+#endif
+
+#ifndef GL_MAX_DRAW_BUFFERS
+#define GL_MAX_DRAW_BUFFERS 0x8824
+#endif
+
+#ifndef GL_TEXTURE_COMPARE_MODE
+#define GL_TEXTURE_COMPARE_MODE 0x884C
+#endif
+
+#ifndef GL_COMPARE_REF_TO_TEXTURE
+#define GL_COMPARE_REF_TO_TEXTURE 0x884E
+#endif
+
+#ifndef GL_TEXTURE_COMPARE_FUNC
+#define GL_TEXTURE_COMPARE_FUNC 0x884D
+#endif
+
+#ifndef GL_MAX_SAMPLES
+#define GL_MAX_SAMPLES 0x8D57
+#endif
+
+/*!
+ Constructs a new QRhiGles2InitParams.
+
+ \l format is set to QSurfaceFormat::defaultFormat().
+ */
+QRhiGles2InitParams::QRhiGles2InitParams()
+{
+ format = QSurfaceFormat::defaultFormat();
+}
+
+/*!
+ \return the QSurfaceFormat that will be set on the QOpenGLContext before
+ calling QOpenGLContext::create(). This format is based on \a format, but
+ may be adjusted. Applicable only when QRhi creates the context.
+ Applications are advised to set this format on their QWindow in order to
+ avoid potential BAD_MATCH failures.
+ */
+QSurfaceFormat QRhiGles2InitParams::adjustedFormat(const QSurfaceFormat &format)
+{
+ QSurfaceFormat fmt = format;
+
+ if (fmt.depthBufferSize() == -1)
+ fmt.setDepthBufferSize(24);
+ if (fmt.stencilBufferSize() == -1)
+ fmt.setStencilBufferSize(8);
+
+ return fmt;
+}
+
+/*!
+ \return a new QOffscreenSurface that can be used with a QRhi by passing it
+ via a QRhiGles2InitParams.
+
+ \a format is adjusted as appropriate in order to avoid having problems
+ afterwards due to an incompatible context and surface.
+
+ \note This function must only be called on the gui/main thread.
+
+ \note It is the application's responsibility to destroy the returned
+ QOffscreenSurface on the gui/main thread once the associated QRhi has been
+ destroyed. The QRhi will not destroy the QOffscreenSurface.
+ */
+QOffscreenSurface *QRhiGles2InitParams::newFallbackSurface(const QSurfaceFormat &format)
+{
+ QSurfaceFormat fmt = adjustedFormat(format);
+
+ // To resolve all fields in the format as much as possible, create a context.
+ // This may be heavy, but allows avoiding BAD_MATCH on some systems.
+ QOpenGLContext tempContext;
+ tempContext.setFormat(fmt);
+ if (tempContext.create())
+ fmt = tempContext.format();
+ else
+ qWarning("QRhiGles2: Failed to create temporary context");
+
+ QOffscreenSurface *s = new QOffscreenSurface;
+ s->setFormat(fmt);
+ s->create();
+
+ return s;
+}
+
+QRhiGles2::QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice)
+ : ofr(this)
+{
+ requestedFormat = QRhiGles2InitParams::adjustedFormat(params->format);
+ fallbackSurface = params->fallbackSurface;
+ maybeWindow = params->window; // may be null
+
+ importedContext = importDevice != nullptr;
+ if (importedContext) {
+ ctx = importDevice->context;
+ if (!ctx) {
+ qWarning("No OpenGL context given, cannot import");
+ importedContext = false;
+ }
+ }
+}
+
+bool QRhiGles2::ensureContext(QSurface *surface) const
+{
+ bool nativeWindowGone = false;
+ if (surface && surface->surfaceClass() == QSurface::Window && !surface->surfaceHandle()) {
+ surface = fallbackSurface;
+ nativeWindowGone = true;
+ }
+
+ if (!surface)
+ surface = fallbackSurface;
+
+ if (needsMakeCurrent)
+ needsMakeCurrent = false;
+ else if (!nativeWindowGone && QOpenGLContext::currentContext() == ctx && (surface == fallbackSurface || ctx->surface() == surface))
+ return true;
+
+ if (!ctx->makeCurrent(surface)) {
+ qWarning("QRhiGles2: Failed to make context current. Expect bad things to happen.");
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiGles2::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(fallbackSurface);
+
+ if (!importedContext) {
+ ctx = new QOpenGLContext;
+ ctx->setFormat(requestedFormat);
+ if (!ctx->create()) {
+ qWarning("QRhiGles2: Failed to create context");
+ delete ctx;
+ ctx = nullptr;
+ return false;
+ }
+ qDebug() << "Created OpenGL context" << ctx->format();
+ }
+
+ if (!ensureContext(maybeWindow ? maybeWindow : fallbackSurface)) // see 'window' discussion in QRhiGles2InitParams comments
+ return false;
+
+ f = static_cast<QOpenGLExtensions *>(ctx->extraFunctions());
+
+ const char *vendor = reinterpret_cast<const char *>(f->glGetString(GL_VENDOR));
+ const char *renderer = reinterpret_cast<const char *>(f->glGetString(GL_RENDERER));
+ const char *version = reinterpret_cast<const char *>(f->glGetString(GL_VERSION));
+ if (vendor && renderer && version)
+ qDebug("OpenGL VENDOR: %s RENDERER: %s VERSION: %s", vendor, renderer, version);
+
+ const QSurfaceFormat actualFormat = ctx->format();
+
+ caps.ctxMajor = actualFormat.majorVersion();
+ caps.ctxMinor = actualFormat.minorVersion();
+
+ GLint n = 0;
+ f->glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &n);
+ supportedCompressedFormats.resize(n);
+ if (n > 0)
+ f->glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, supportedCompressedFormats.data());
+
+ f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &caps.maxTextureSize);
+
+ if (caps.ctxMajor >= 3 || actualFormat.renderableType() == QSurfaceFormat::OpenGL) {
+ f->glGetIntegerv(GL_MAX_DRAW_BUFFERS, &caps.maxDrawBuffers);
+ f->glGetIntegerv(GL_MAX_SAMPLES, &caps.maxSamples);
+ caps.maxSamples = qMax(1, caps.maxSamples);
+ } else {
+ caps.maxDrawBuffers = 1;
+ caps.maxSamples = 1;
+ }
+
+ caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)
+ && f->hasOpenGLExtension(QOpenGLExtensions::FramebufferBlit);
+
+ caps.npotTexture = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures);
+ caps.npotTextureRepeat = f->hasOpenGLFeature(QOpenGLFunctions::NPOTTextureRepeat);
+
+ caps.gles = actualFormat.renderableType() == QSurfaceFormat::OpenGLES;
+ if (caps.gles)
+ caps.fixedIndexPrimitiveRestart = caps.ctxMajor >= 3;
+ else
+ caps.fixedIndexPrimitiveRestart = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3);
+
+ if (caps.fixedIndexPrimitiveRestart)
+ f->glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
+
+ caps.bgraExternalFormat = f->hasOpenGLExtension(QOpenGLExtensions::BGRATextureFormat);
+ caps.bgraInternalFormat = caps.bgraExternalFormat && caps.gles;
+ caps.r8Format = f->hasOpenGLFeature(QOpenGLFunctions::TextureRGFormats);
+ caps.r16Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized16Formats);
+ caps.floatFormats = caps.ctxMajor >= 3;
+ caps.depthTexture = caps.ctxMajor >= 3;
+ caps.packedDepthStencil = f->hasOpenGLExtension(QOpenGLExtensions::PackedDepthStencil);
+#ifdef Q_OS_WASM
+ caps.needsDepthStencilCombinedAttach = true;
+#else
+ caps.needsDepthStencilCombinedAttach = false;
+#endif
+ caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer);
+ caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile;
+ caps.uniformBuffers = caps.ctxMajor >= 3 && (caps.gles || caps.ctxMinor >= 1);
+ caps.elementIndexUint = f->hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint);
+ caps.depth24 = f->hasOpenGLExtension(QOpenGLExtensions::Depth24);
+ caps.rgba8Format = f->hasOpenGLExtension(QOpenGLExtensions::Sized8Formats);
+ caps.instancing = caps.ctxMajor >= 3 && (caps.gles || caps.ctxMinor >= 3);
+ caps.baseVertex = caps.ctxMajor >= 3 && caps.ctxMinor >= 2;
+
+ nativeHandlesStruct.context = ctx;
+
+ return true;
+}
+
+void QRhiGles2::destroy()
+{
+ if (!f)
+ return;
+
+ ensureContext();
+ executeDeferredReleases();
+
+ if (!importedContext) {
+ delete ctx;
+ ctx = nullptr;
+ }
+
+ f = nullptr;
+}
+
+void QRhiGles2::executeDeferredReleases()
+{
+ for (int i = releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiGles2::DeferredReleaseEntry &e(releaseQueue[i]);
+ switch (e.type) {
+ case QRhiGles2::DeferredReleaseEntry::Buffer:
+ f->glDeleteBuffers(1, &e.buffer.buffer);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::Pipeline:
+ f->glDeleteProgram(e.pipeline.program);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::Texture:
+ f->glDeleteTextures(1, &e.texture.texture);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::RenderBuffer:
+ f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer);
+ f->glDeleteRenderbuffers(1, &e.renderbuffer.renderbuffer2);
+ break;
+ case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget:
+ f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer);
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ releaseQueue.removeAt(i);
+ }
+}
+
+QVector<int> QRhiGles2::supportedSampleCounts() const
+{
+ if (supportedSampleCountList.isEmpty()) {
+ // 1, 2, 4, 8, ...
+ for (int i = 1; i <= caps.maxSamples; i *= 2)
+ supportedSampleCountList.append(i);
+ }
+ return supportedSampleCountList;
+}
+
+int QRhiGles2::effectiveSampleCount(int sampleCount) const
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ const int s = qBound(1, sampleCount, 64);
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return 1;
+ }
+ return s;
+}
+
+static inline bool isPowerOfTwo(int x)
+{
+ // Assumption: x >= 1
+ return x == (x & -x);
+}
+
+QSize QRhiGles2::safeTextureSize(const QSize &pixelSize) const
+{
+ QSize size = pixelSize.isEmpty() ? QSize(1, 1) : pixelSize;
+
+ if (!caps.npotTexture) {
+ if (!isPowerOfTwo(size.width())) {
+ qWarning("Texture width %d is not a power of two, adjusting",
+ size.width());
+ size.setWidth(qNextPowerOfTwo(size.width()));
+ }
+ if (!isPowerOfTwo(size.height())) {
+ qWarning("Texture height %d is not a power of two, adjusting",
+ size.height());
+ size.setHeight(qNextPowerOfTwo(size.height()));
+ }
+ }
+
+ if (size.width() > caps.maxTextureSize) {
+ qWarning("Texture width %d exceeds maximum width %d, adjusting",
+ size.width(), caps.maxTextureSize);
+ size.setWidth(caps.maxTextureSize);
+ }
+ if (size.height() > caps.maxTextureSize) {
+ qWarning("Texture height %d exceeds maximum height %d, adjusting",
+ size.height(), caps.maxTextureSize);
+ size.setHeight(caps.maxTextureSize);
+ }
+
+ return size;
+}
+
+QRhiSwapChain *QRhiGles2::createSwapChain()
+{
+ return new QGles2SwapChain(this);
+}
+
+QRhiBuffer *QRhiGles2::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QGles2Buffer(this, type, usage, size);
+}
+
+int QRhiGles2::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiGles2::isYUpInFramebuffer() const
+{
+ return true;
+}
+
+bool QRhiGles2::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiGles2::isClipDepthZeroToOne() const
+{
+ return false;
+}
+
+QMatrix4x4 QRhiGles2::clipSpaceCorrMatrix() const
+{
+ return QMatrix4x4(); // identity
+}
+
+static inline GLenum toGlCompressedTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::BC1:
+ return srgb ? 0x8C4C : 0x83F0;
+ case QRhiTexture::BC3:
+ return srgb ? 0x8C4E : 0x83F2;
+ case QRhiTexture::BC5:
+ return srgb ? 0x8C4F : 0x83F3;
+
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? 0x9275 : 0x9274;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? 0x9277 : 0x9276;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? 0x9279 : 0x9278;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? 0x93D0 : 0x93B0;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? 0x93D1 : 0x93B1;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? 0x93D2 : 0x93B2;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? 0x93D3 : 0x93B3;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? 0x93D4 : 0x93B4;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? 0x93D5 : 0x93B5;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? 0x93D6 : 0x93B6;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? 0x93D7 : 0x93B7;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? 0x93D8 : 0x93B8;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? 0x93D9 : 0x93B9;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? 0x93DA : 0x93BA;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? 0x93DB : 0x93BB;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? 0x93DC : 0x93BC;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? 0x93DD : 0x93BD;
+
+ default:
+ return 0; // this is reachable, just return an invalid format
+ }
+}
+
+bool QRhiGles2::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ if (isCompressedFormat(format))
+ return supportedCompressedFormats.contains(toGlCompressedTextureFormat(format, flags));
+
+ switch (format) {
+ case QRhiTexture::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::D32F:
+ return caps.depthTexture;
+
+ case QRhiTexture::BGRA8:
+ return caps.bgraExternalFormat;
+
+ case QRhiTexture::R8:
+ return caps.r8Format;
+
+ case QRhiTexture::R16:
+ return caps.r16Format;
+
+ case QRhiTexture::RGBA16F:
+ Q_FALLTHROUGH();
+ case QRhiTexture::RGBA32F:
+ return caps.floatFormats;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return false;
+ case QRhi::MultisampleRenderBuffer:
+ return caps.msaaRenderBuffer;
+ case QRhi::DebugMarkers:
+ return false;
+ case QRhi::Timestamps:
+ return false;
+ case QRhi::Instancing:
+ return caps.instancing;
+ case QRhi::CustomInstanceStepRate:
+ return false;
+ case QRhi::PrimitiveRestart:
+ return caps.fixedIndexPrimitiveRestart;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return caps.npotTextureRepeat;
+ case QRhi::RedOrAlpha8IsRed:
+ return caps.coreProfile;
+ case QRhi::ElementIndexUint:
+ return caps.elementIndexUint;
+ case QRhi::Compute:
+ return false;
+ case QRhi::WideLines:
+ return true;
+ case QRhi::VertexShaderPointSize:
+ return true;
+ case QRhi::BaseVertex:
+ return caps.baseVertex;
+ case QRhi::BaseInstance:
+ return false; // not in ES 3.2, so won't bother
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiGles2::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return caps.maxTextureSize;
+ case QRhi::MaxColorAttachments:
+ return caps.maxDrawBuffers;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiGles2::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiGles2::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+void QRhiGles2::makeThreadLocalNativeContextCurrent()
+{
+ if (inFrame && !ofr.active)
+ ensureContext(currentSwapChain->surface);
+ else
+ ensureContext();
+}
+
+QRhiRenderBuffer *QRhiGles2::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QGles2RenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiGles2::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QGles2Texture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiGles2::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QGles2Sampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiGles2::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QGles2TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiGles2::createGraphicsPipeline()
+{
+ return new QGles2GraphicsPipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiGles2::createShaderResourceBindings()
+{
+ return new QGles2ShaderResourceBindings(this);
+}
+
+QRhiComputePipeline *QRhiGles2::createComputePipeline()
+{
+ return new QGles2ComputePipeline(this);
+}
+
+void QRhiGles2::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+ const bool pipelineChanged = cbD->currentPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+ if (pipelineChanged) {
+ cbD->currentPipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindGraphicsPipeline;
+ cmd.args.bindGraphicsPipeline.ps = ps;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+ Q_ASSERT(cbD->currentPipeline);
+
+ if (!srb)
+ srb = QRHI_RES(QGles2GraphicsPipeline, cbD->currentPipeline)->m_shaderResourceBindings;
+
+ QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
+ bool hasDynamicOffsetInSrb = false;
+ for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->m_bindings[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ const bool srbChanged = cbD->currentSrb != srb || cbD->currentSrbGeneration != srbD->generation;
+
+ if (srbChanged || hasDynamicOffsetInSrb) {
+ cbD->currentSrb = srb;
+ cbD->currentSrbGeneration = srbD->generation;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindShaderResources;
+ cmd.args.bindShaderResources.ps = cbD->currentPipeline;
+ cmd.args.bindShaderResources.srb = srb;
+ cmd.args.bindShaderResources.dynamicOffsetCount = 0;
+ if (hasDynamicOffsetInSrb) {
+ if (dynamicOffsetCount < QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS) {
+ cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
+ uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ *p++ = dynOfs.first;
+ *p++ = dynOfs.second;
+ }
+ } else {
+ qWarning("Too many dynamic offsets (%d, max is %d)",
+ dynamicOffsetCount, QGles2CommandBuffer::Command::MAX_UBUF_BINDINGS);
+ }
+ }
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ for (int i = 0; i < bindingCount; ++i) {
+ QRhiBuffer *buf = bindings[i].first;
+ quint32 ofs = bindings[i].second;
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindVertexBuffer;
+ cmd.args.bindVertexBuffer.ps = cbD->currentPipeline;
+ cmd.args.bindVertexBuffer.buffer = bufD->buffer;
+ cmd.args.bindVertexBuffer.offset = ofs;
+ cmd.args.bindVertexBuffer.binding = startBinding + i;
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QGles2Buffer *ibufD = QRHI_RES(QGles2Buffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
+ cmd.args.bindIndexBuffer.offset = indexOffset;
+ cmd.args.bindIndexBuffer.type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiGles2::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Viewport;
+ const std::array<float, 4> r = viewport.viewport();
+ cmd.args.viewport.x = qMax(0.0f, r[0]);
+ cmd.args.viewport.y = qMax(0.0f, r[1]);
+ cmd.args.viewport.w = r[2];
+ cmd.args.viewport.h = r[3];
+ cmd.args.viewport.d0 = viewport.minDepth();
+ cmd.args.viewport.d1 = viewport.maxDepth();
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Scissor;
+ const std::array<int, 4> r = scissor.scissor();
+ cmd.args.scissor.x = qMax(0, r[0]);
+ cmd.args.scissor.y = qMax(0, r[1]);
+ cmd.args.scissor.w = r[2];
+ cmd.args.scissor.h = r[3];
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BlendConstants;
+ cmd.args.blendConstants.r = c.redF();
+ cmd.args.blendConstants.g = c.greenF();
+ cmd.args.blendConstants.b = c.blueF();
+ cmd.args.blendConstants.a = c.alphaF();
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::StencilRef;
+ cmd.args.stencilRef.ref = refValue;
+ cmd.args.stencilRef.ps = cbD->currentPipeline;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::Draw;
+ cmd.args.draw.ps = cbD->currentPipeline;
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cmd.args.draw.instanceCount = instanceCount;
+ cmd.args.draw.baseInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.ps = cbD->currentPipeline;
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cmd.args.drawIndexed.instanceCount = instanceCount;
+ cmd.args.drawIndexed.baseInstance = firstInstance;
+ cmd.args.drawIndexed.baseVertex = vertexOffset;
+ cbD->commands.append(cmd);
+}
+
+void QRhiGles2::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+ Q_UNUSED(name);
+}
+
+void QRhiGles2::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+}
+
+void QRhiGles2::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers)
+ return;
+
+ Q_UNUSED(cb);
+ Q_UNUSED(msg);
+}
+
+const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiGles2::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ flushCommandBuffer(); // also ensures the context is current
+}
+
+void QRhiGles2::endExternal(QRhiCommandBuffer *cb)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->commands.isEmpty());
+ cbD->resetCachedState();
+ if (cbD->currentTarget)
+ enqueueBindFramebuffer(cbD->currentTarget, cbD);
+}
+
+static void addBoundaryCommand(QGles2CommandBuffer *cb, QGles2CommandBuffer::Command::Cmd type)
+{
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = type;
+ cb->commands.append(cmd);
+}
+
+QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+
+ QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
+ if (!ensureContext(swapChainD->surface))
+ return QRhi::FrameOpError;
+
+ currentSwapChain = swapChainD;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ executeDeferredReleases();
+ swapChainD->cb.resetState();
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame);
+
+ if (!ensureContext(swapChainD->surface))
+ return QRhi::FrameOpError;
+
+ executeCommandBuffer(&swapChainD->cb);
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the swap
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (swapChainD->surface && !flags.testFlag(QRhi::SkipPresent)) {
+ ctx->swapBuffers(swapChainD->surface);
+ needsMakeCurrent = true;
+ } else {
+ f->glFlush();
+ }
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+
+ ofr.active = true;
+
+ executeDeferredReleases();
+ ofr.cbWrapper.resetState();
+
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame);
+ *cb = &ofr.cbWrapper;
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::endOffscreenFrame()
+{
+ Q_ASSERT(ofr.active);
+ ofr.active = false;
+
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame);
+
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+
+ executeCommandBuffer(&ofr.cbWrapper);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::finish()
+{
+ return inFrame ? flushCommandBuffer() : QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiGles2::flushCommandBuffer()
+{
+ if (ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ if (!ensureContext())
+ return QRhi::FrameOpError;
+ executeCommandBuffer(&ofr.cbWrapper);
+ ofr.cbWrapper.resetCommands();
+ } else {
+ Q_ASSERT(currentSwapChain);
+ if (!ensureContext(currentSwapChain->surface))
+ return QRhi::FrameOpError;
+ executeCommandBuffer(&currentSwapChain->cb);
+ currentSwapChain->cb.resetCommands();
+ }
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
+{
+ const bool isCompressed = isCompressedFormat(texD->m_format);
+ const bool isCubeMap = texD->m_flags.testFlag(QRhiTexture::CubeMap);
+ const GLenum faceTargetBase = isCubeMap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ const QPoint dp = subresDesc.destinationTopLeft();
+ const QByteArray rawData = subresDesc.data();
+ if (!subresDesc.image().isNull()) {
+ QImage img = subresDesc.image();
+ QSize size = img.size();
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::SubImage;
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const QPoint sp = subresDesc.sourceTopLeft();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ }
+ cmd.args.subImage.target = texD->target;
+ cmd.args.subImage.texture = texD->texture;
+ cmd.args.subImage.faceTarget = faceTargetBase + layer;
+ cmd.args.subImage.level = level;
+ cmd.args.subImage.dx = dp.x();
+ cmd.args.subImage.dy = dp.y();
+ cmd.args.subImage.w = size.width();
+ cmd.args.subImage.h = size.height();
+ cmd.args.subImage.glformat = texD->glformat;
+ cmd.args.subImage.gltype = texD->gltype;
+ cmd.args.subImage.rowStartAlign = 4;
+ cmd.args.subImage.data = cbD->retainImage(img);
+ cbD->commands.append(cmd);
+ } else if (!rawData.isEmpty() && isCompressed) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ if (texD->specified) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CompressedSubImage;
+ cmd.args.compressedSubImage.target = texD->target;
+ cmd.args.compressedSubImage.texture = texD->texture;
+ cmd.args.compressedSubImage.faceTarget = faceTargetBase + layer;
+ cmd.args.compressedSubImage.level = level;
+ cmd.args.compressedSubImage.dx = dp.x();
+ cmd.args.compressedSubImage.dy = dp.y();
+ cmd.args.compressedSubImage.w = size.width();
+ cmd.args.compressedSubImage.h = size.height();
+ cmd.args.compressedSubImage.glintformat = texD->glintformat;
+ cmd.args.compressedSubImage.size = rawData.size();
+ cmd.args.compressedSubImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CompressedImage;
+ cmd.args.compressedImage.target = texD->target;
+ cmd.args.compressedImage.texture = texD->texture;
+ cmd.args.compressedImage.faceTarget = faceTargetBase + layer;
+ cmd.args.compressedImage.level = level;
+ cmd.args.compressedImage.glintformat = texD->glintformat;
+ cmd.args.compressedImage.w = size.width();
+ cmd.args.compressedImage.h = size.height();
+ cmd.args.compressedImage.size = rawData.size();
+ cmd.args.compressedImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ }
+ } else if (!rawData.isEmpty()) {
+ const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ quint32 bpl = 0;
+ textureFormatInfo(texD->m_format, size, &bpl, nullptr);
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::SubImage;
+ cmd.args.subImage.target = texD->target;
+ cmd.args.subImage.texture = texD->texture;
+ cmd.args.subImage.faceTarget = faceTargetBase + layer;
+ cmd.args.subImage.level = level;
+ cmd.args.subImage.dx = dp.x();
+ cmd.args.subImage.dy = dp.y();
+ cmd.args.subImage.w = size.width();
+ cmd.args.subImage.h = size.height();
+ cmd.args.subImage.glformat = texD->glformat;
+ cmd.args.subImage.gltype = texD->gltype;
+ // Default unpack alignment (row start aligment
+ // requirement) is 4. QImage guarantees 4 byte aligned
+ // row starts, but our raw data here does not.
+ cmd.args.subImage.rowStartAlign = (bpl & 3) ? 1 : 4;
+ cmd.args.subImage.data = cbD->retainData(rawData);
+ cbD->commands.append(cmd);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+}
+
+void QRhiGles2::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size());
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
+ cmd.args.bufferSubData.target = bufD->target;
+ cmd.args.bufferSubData.buffer = bufD->buffer;
+ cmd.args.bufferSubData.offset = u.offset;
+ cmd.args.bufferSubData.size = u.data.size();
+ cmd.args.bufferSubData.data = cbD->retainData(u.data);
+ cbD->commands.append(cmd);
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ if (bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ memcpy(bufD->ubuf.data() + u.offset, u.data.constData(), u.data.size());
+ } else {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BufferSubData;
+ cmd.args.bufferSubData.target = bufD->target;
+ cmd.args.bufferSubData.buffer = bufD->buffer;
+ cmd.args.bufferSubData.offset = u.offset;
+ cmd.args.bufferSubData.size = u.data.size();
+ cmd.args.bufferSubData.data = cbD->retainData(u.data);
+ cbD->commands.append(cmd);
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.upload.tex);
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
+ }
+ }
+ texD->specified = true;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QGles2Texture *srcD = QRHI_RES(QGles2Texture, u.copy.src);
+ QGles2Texture *dstD = QRHI_RES(QGles2Texture, u.copy.dst);
+
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ // do not translate coordinates, even if sp is bottom-left from gl's pov
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+
+ const GLenum srcFaceTargetBase = srcD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : srcD->target;
+ const GLenum dstFaceTargetBase = dstD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : dstD->target;
+
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::CopyTex;
+
+ cmd.args.copyTex.srcFaceTarget = srcFaceTargetBase + u.copy.desc.sourceLayer();
+ cmd.args.copyTex.srcTexture = srcD->texture;
+ cmd.args.copyTex.srcLevel = u.copy.desc.sourceLevel();
+ cmd.args.copyTex.srcX = sp.x();
+ cmd.args.copyTex.srcY = sp.y();
+
+ cmd.args.copyTex.dstTarget = dstD->target;
+ cmd.args.copyTex.dstTexture = dstD->texture;
+ cmd.args.copyTex.dstFaceTarget = dstFaceTargetBase + u.copy.desc.destinationLayer();
+ cmd.args.copyTex.dstLevel = u.copy.desc.destinationLevel();
+ cmd.args.copyTex.dstX = dp.x();
+ cmd.args.copyTex.dstY = dp.y();
+
+ cmd.args.copyTex.w = size.width();
+ cmd.args.copyTex.h = size.height();
+
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::ReadPixels;
+ cmd.args.readPixels.result = u.read.result;
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.read.rb.texture());
+ cmd.args.readPixels.texture = texD ? texD->texture : 0;
+ if (texD) {
+ cmd.args.readPixels.w = texD->m_pixelSize.width();
+ cmd.args.readPixels.h = texD->m_pixelSize.height();
+ cmd.args.readPixels.format = texD->m_format;
+ const GLenum faceTargetBase = texD->m_flags.testFlag(QRhiTexture::CubeMap)
+ ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ cmd.args.readPixels.readTarget = faceTargetBase + u.read.rb.layer();
+ cmd.args.readPixels.level = u.read.rb.level();
+ }
+ cbD->commands.append(cmd);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::GenMip;
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, u.mipgen.tex);
+ cmd.args.genMip.target = texD->target;
+ cmd.args.genMip.texture = texD->texture;
+ cbD->commands.append(cmd);
+ }
+ }
+
+ ud->free();
+}
+
+static inline GLenum toGlTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return GL_TRIANGLES;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return GL_TRIANGLE_STRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return GL_LINES;
+ case QRhiGraphicsPipeline::LineStrip:
+ return GL_LINE_STRIP;
+ case QRhiGraphicsPipeline::Points:
+ return GL_POINTS;
+ default:
+ Q_UNREACHABLE();
+ return GL_TRIANGLES;
+ }
+}
+
+static inline GLenum toGlCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::Front:
+ return GL_FRONT;
+ case QRhiGraphicsPipeline::Back:
+ return GL_BACK;
+ default:
+ Q_UNREACHABLE();
+ return GL_BACK;
+ }
+}
+
+static inline GLenum toGlFrontFace(QRhiGraphicsPipeline::FrontFace f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::CCW:
+ return GL_CCW;
+ case QRhiGraphicsPipeline::CW:
+ return GL_CW;
+ default:
+ Q_UNREACHABLE();
+ return GL_CCW;
+ }
+}
+
+static inline GLenum toGlBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return GL_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return GL_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return GL_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return GL_ONE_MINUS_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return GL_DST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return GL_ONE_MINUS_DST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return GL_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return GL_ONE_MINUS_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return GL_DST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return GL_ONE_MINUS_DST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return GL_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return GL_ONE_MINUS_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return GL_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return GL_ONE_MINUS_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return GL_SRC_ALPHA_SATURATE;
+ case QRhiGraphicsPipeline::Src1Color:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::Src1Alpha:
+ Q_FALLTHROUGH();
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ qWarning("Unsupported blend factor %d", f);
+ return GL_ZERO;
+ default:
+ Q_UNREACHABLE();
+ return GL_ZERO;
+ }
+}
+
+static inline GLenum toGlBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return GL_FUNC_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return GL_FUNC_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return GL_FUNC_REVERSE_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return GL_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return GL_MAX;
+ default:
+ Q_UNREACHABLE();
+ return GL_FUNC_ADD;
+ }
+}
+
+static inline GLenum toGlCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return GL_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return GL_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return GL_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return GL_LEQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return GL_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return GL_NOTEQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return GL_GEQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return GL_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return GL_ALWAYS;
+ }
+}
+
+static inline GLenum toGlStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return GL_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return GL_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return GL_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return GL_INCR;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return GL_DECR;
+ case QRhiGraphicsPipeline::Invert:
+ return GL_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return GL_INCR_WRAP;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return GL_DECR_WRAP;
+ default:
+ Q_UNREACHABLE();
+ return GL_KEEP;
+ }
+}
+
+static inline GLenum toGlMinFilter(QRhiSampler::Filter f, QRhiSampler::Filter m)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ if (m == QRhiSampler::None)
+ return GL_NEAREST;
+ else
+ return m == QRhiSampler::Nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_LINEAR;
+ case QRhiSampler::Linear:
+ if (m == QRhiSampler::None)
+ return GL_LINEAR;
+ else
+ return m == QRhiSampler::Nearest ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return GL_LINEAR;
+ }
+}
+
+static inline GLenum toGlMagFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return GL_NEAREST;
+ case QRhiSampler::Linear:
+ return GL_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return GL_LINEAR;
+ }
+}
+
+static inline GLenum toGlWrapMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return GL_REPEAT;
+ case QRhiSampler::ClampToEdge:
+ return GL_CLAMP_TO_EDGE;
+ case QRhiSampler::Mirror:
+ return GL_MIRRORED_REPEAT;
+ case QRhiSampler::MirrorOnce:
+ Q_FALLTHROUGH();
+ case QRhiSampler::Border:
+ qWarning("Unsupported wrap mode %d", m);
+ return GL_CLAMP_TO_EDGE;
+ default:
+ Q_UNREACHABLE();
+ return GL_CLAMP_TO_EDGE;
+ }
+}
+
+static inline GLenum toGlTextureCompareFunc(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return GL_NEVER;
+ case QRhiSampler::Less:
+ return GL_LESS;
+ case QRhiSampler::Equal:
+ return GL_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return GL_LEQUAL;
+ case QRhiSampler::Greater:
+ return GL_GREATER;
+ case QRhiSampler::NotEqual:
+ return GL_NOTEQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return GL_GEQUAL;
+ case QRhiSampler::Always:
+ return GL_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return GL_NEVER;
+ }
+}
+
+void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ GLenum indexType = GL_UNSIGNED_SHORT;
+ quint32 indexStride = sizeof(quint16);
+ quint32 indexOffset = 0;
+
+ for (const QGles2CommandBuffer::Command &cmd : qAsConst(cbD->commands)) {
+ switch (cmd.cmd) {
+ case QGles2CommandBuffer::Command::BeginFrame:
+ if (caps.coreProfile) {
+ if (!vao)
+ f->glGenVertexArrays(1, &vao);
+ f->glBindVertexArray(vao);
+ }
+ break;
+ case QGles2CommandBuffer::Command::EndFrame:
+ if (vao)
+ f->glBindVertexArray(0);
+ break;
+ case QGles2CommandBuffer::Command::Viewport:
+ f->glViewport(cmd.args.viewport.x, cmd.args.viewport.y, cmd.args.viewport.w, cmd.args.viewport.h);
+ f->glDepthRangef(cmd.args.viewport.d0, cmd.args.viewport.d1);
+ break;
+ case QGles2CommandBuffer::Command::Scissor:
+ f->glScissor(cmd.args.scissor.x, cmd.args.scissor.y, cmd.args.scissor.w, cmd.args.scissor.h);
+ break;
+ case QGles2CommandBuffer::Command::BlendConstants:
+ f->glBlendColor(cmd.args.blendConstants.r, cmd.args.blendConstants.g, cmd.args.blendConstants.b, cmd.args.blendConstants.a);
+ break;
+ case QGles2CommandBuffer::Command::StencilRef:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.stencilRef.ps);
+ if (psD) {
+ f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask);
+ f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), cmd.args.stencilRef.ref, psD->m_stencilReadMask);
+ } else {
+ qWarning("No graphics pipeline active for setStencilRef; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindVertexBuffer:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.bindVertexBuffer.ps);
+ if (psD) {
+ const QVector<QRhiVertexInputBinding> bindings = psD->m_vertexInputLayout.bindings();
+ const QVector<QRhiVertexInputAttribute> attributes = psD->m_vertexInputLayout.attributes();
+ for (const QRhiVertexInputAttribute &a : attributes) {
+ const int bindingIdx = a.binding();
+ if (bindingIdx != cmd.args.bindVertexBuffer.binding)
+ continue;
+
+ // we do not support more than one vertex buffer
+ f->glBindBuffer(GL_ARRAY_BUFFER, cmd.args.bindVertexBuffer.buffer);
+
+ const int stride = bindings[bindingIdx].stride();
+ int size = 1;
+ GLenum type = GL_FLOAT;
+ bool normalize = false;
+ switch (a.format()) {
+ case QRhiVertexInputAttribute::Float4:
+ type = GL_FLOAT;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::Float3:
+ type = GL_FLOAT;
+ size = 3;
+ break;
+ case QRhiVertexInputAttribute::Float2:
+ type = GL_FLOAT;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::Float:
+ type = GL_FLOAT;
+ size = 1;
+ break;
+ case QRhiVertexInputAttribute::UNormByte4:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::UNormByte2:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::UNormByte:
+ type = GL_UNSIGNED_BYTE;
+ normalize = true;
+ size = 1;
+ break;
+ default:
+ break;
+ }
+
+ const int locationIdx = a.location();
+ quint32 ofs = a.offset() + cmd.args.bindVertexBuffer.offset;
+ f->glVertexAttribPointer(locationIdx, size, type, normalize, stride,
+ reinterpret_cast<const GLvoid *>(quintptr(ofs)));
+ f->glEnableVertexAttribArray(locationIdx);
+ if (bindings[bindingIdx].classification() == QRhiVertexInputBinding::PerInstance
+ && caps.instancing)
+ {
+ f->glVertexAttribDivisor(locationIdx, bindings[bindingIdx].instanceStepRate());
+ }
+ }
+ } else {
+ qWarning("No graphics pipeline active for setVertexInput; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindIndexBuffer:
+ indexType = cmd.args.bindIndexBuffer.type;
+ indexStride = indexType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32);
+ indexOffset = cmd.args.bindIndexBuffer.offset;
+ f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cmd.args.bindIndexBuffer.buffer);
+ break;
+ case QGles2CommandBuffer::Command::Draw:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.draw.ps);
+ if (psD) {
+ if (cmd.args.draw.instanceCount == 1 || !caps.instancing) {
+ f->glDrawArrays(psD->drawMode, cmd.args.draw.firstVertex, cmd.args.draw.vertexCount);
+ } else {
+ f->glDrawArraysInstanced(psD->drawMode, cmd.args.draw.firstVertex, cmd.args.draw.vertexCount,
+ cmd.args.draw.instanceCount);
+ }
+ } else {
+ qWarning("No graphics pipeline active for draw; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::DrawIndexed:
+ {
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, cmd.args.drawIndexed.ps);
+ if (psD) {
+ const GLvoid *ofs = reinterpret_cast<const GLvoid *>(
+ quintptr(cmd.args.drawIndexed.firstIndex * indexStride + indexOffset));
+ if (cmd.args.drawIndexed.instanceCount == 1 || !caps.instancing) {
+ if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) {
+ f->glDrawElementsBaseVertex(psD->drawMode,
+ cmd.args.drawIndexed.indexCount,
+ indexType,
+ ofs,
+ cmd.args.drawIndexed.baseVertex);
+ } else {
+ f->glDrawElements(psD->drawMode,
+ cmd.args.drawIndexed.indexCount,
+ indexType,
+ ofs);
+ }
+ } else {
+ if (cmd.args.drawIndexed.baseVertex != 0 && caps.baseVertex) {
+ f->glDrawElementsInstancedBaseVertex(psD->drawMode,
+ cmd.args.drawIndexed.indexCount,
+ indexType,
+ ofs,
+ cmd.args.drawIndexed.instanceCount,
+ cmd.args.drawIndexed.baseVertex);
+ } else {
+ f->glDrawElementsInstanced(psD->drawMode,
+ cmd.args.drawIndexed.indexCount,
+ indexType,
+ ofs,
+ cmd.args.drawIndexed.instanceCount);
+ }
+ }
+ } else {
+ qWarning("No graphics pipeline active for drawIndexed; ignored");
+ }
+ }
+ break;
+ case QGles2CommandBuffer::Command::BindGraphicsPipeline:
+ executeBindGraphicsPipeline(cmd.args.bindGraphicsPipeline.ps);
+ break;
+ case QGles2CommandBuffer::Command::BindShaderResources:
+ bindShaderResources(cmd.args.bindShaderResources.ps,
+ cmd.args.bindShaderResources.srb,
+ cmd.args.bindShaderResources.dynamicOffsetPairs,
+ cmd.args.bindShaderResources.dynamicOffsetCount);
+ break;
+ case QGles2CommandBuffer::Command::BindFramebuffer:
+ if (cmd.args.bindFramebuffer.fbo) {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.bindFramebuffer.fbo);
+ if (caps.maxDrawBuffers > 1) {
+ const int colorAttCount = cmd.args.bindFramebuffer.colorAttCount;
+ QVarLengthArray<GLenum, 8> bufs;
+ for (int i = 0; i < colorAttCount; ++i)
+ bufs.append(GL_COLOR_ATTACHMENT0 + i);
+ f->glDrawBuffers(colorAttCount, bufs.constData());
+ }
+ } else {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ if (caps.maxDrawBuffers > 1) {
+ GLenum bufs = GL_BACK;
+ f->glDrawBuffers(1, &bufs);
+ }
+ }
+ if (caps.srgbCapableDefaultFramebuffer) {
+ if (cmd.args.bindFramebuffer.srgb)
+ f->glEnable(GL_FRAMEBUFFER_SRGB);
+ else
+ f->glDisable(GL_FRAMEBUFFER_SRGB);
+ }
+ break;
+ case QGles2CommandBuffer::Command::Clear:
+ f->glDisable(GL_SCISSOR_TEST);
+ if (cmd.args.clear.mask & GL_COLOR_BUFFER_BIT) {
+ f->glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ f->glClearColor(cmd.args.clear.c[0], cmd.args.clear.c[1], cmd.args.clear.c[2], cmd.args.clear.c[3]);
+ }
+ if (cmd.args.clear.mask & GL_DEPTH_BUFFER_BIT) {
+ f->glDepthMask(GL_TRUE);
+ f->glClearDepthf(cmd.args.clear.d);
+ }
+ if (cmd.args.clear.mask & GL_STENCIL_BUFFER_BIT)
+ f->glClearStencil(cmd.args.clear.s);
+ f->glClear(cmd.args.clear.mask);
+ break;
+ case QGles2CommandBuffer::Command::BufferSubData:
+ f->glBindBuffer(cmd.args.bufferSubData.target, cmd.args.bufferSubData.buffer);
+ f->glBufferSubData(cmd.args.bufferSubData.target, cmd.args.bufferSubData.offset, cmd.args.bufferSubData.size,
+ cmd.args.bufferSubData.data);
+ break;
+ case QGles2CommandBuffer::Command::CopyTex:
+ {
+ GLuint fbo;
+ f->glGenFramebuffers(1, &fbo);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.copyTex.srcFaceTarget, cmd.args.copyTex.srcTexture, cmd.args.copyTex.srcLevel);
+ f->glBindTexture(cmd.args.copyTex.dstTarget, cmd.args.copyTex.dstTexture);
+ f->glCopyTexSubImage2D(cmd.args.copyTex.dstFaceTarget, cmd.args.copyTex.dstLevel,
+ cmd.args.copyTex.dstX, cmd.args.copyTex.dstY,
+ cmd.args.copyTex.srcX, cmd.args.copyTex.srcY,
+ cmd.args.copyTex.w, cmd.args.copyTex.h);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ f->glDeleteFramebuffers(1, &fbo);
+ }
+ break;
+ case QGles2CommandBuffer::Command::ReadPixels:
+ {
+ QRhiReadbackResult *result = cmd.args.readPixels.result;
+ GLuint tex = cmd.args.readPixels.texture;
+ GLuint fbo = 0;
+ if (tex) {
+ result->pixelSize = QSize(cmd.args.readPixels.w, cmd.args.readPixels.h);
+ result->format = cmd.args.readPixels.format;
+ f->glGenFramebuffers(1, &fbo);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.readPixels.readTarget, cmd.args.readPixels.texture, cmd.args.readPixels.level);
+ } else {
+ result->pixelSize = currentSwapChain->pixelSize;
+ result->format = QRhiTexture::RGBA8;
+ // readPixels handles multisample resolving implicitly
+ }
+ result->data.resize(result->pixelSize.width() * result->pixelSize.height() * 4);
+ // With GLES (2.0?) GL_RGBA is the only mandated readback format, so stick with it.
+ f->glReadPixels(0, 0, result->pixelSize.width(), result->pixelSize.height(),
+ GL_RGBA, GL_UNSIGNED_BYTE,
+ result->data.data());
+ if (fbo) {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ f->glDeleteFramebuffers(1, &fbo);
+ }
+ if (result->completed)
+ result->completed();
+ }
+ break;
+ case QGles2CommandBuffer::Command::SubImage:
+ f->glBindTexture(cmd.args.subImage.target, cmd.args.subImage.texture);
+ if (cmd.args.subImage.rowStartAlign != 4)
+ f->glPixelStorei(GL_UNPACK_ALIGNMENT, cmd.args.subImage.rowStartAlign);
+ f->glTexSubImage2D(cmd.args.subImage.faceTarget, cmd.args.subImage.level,
+ cmd.args.subImage.dx, cmd.args.subImage.dy,
+ cmd.args.subImage.w, cmd.args.subImage.h,
+ cmd.args.subImage.glformat, cmd.args.subImage.gltype,
+ cmd.args.subImage.data);
+ if (cmd.args.subImage.rowStartAlign != 4)
+ f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ break;
+ case QGles2CommandBuffer::Command::CompressedImage:
+ f->glBindTexture(cmd.args.compressedImage.target, cmd.args.compressedImage.texture);
+ f->glCompressedTexImage2D(cmd.args.compressedImage.faceTarget, cmd.args.compressedImage.level,
+ cmd.args.compressedImage.glintformat,
+ cmd.args.compressedImage.w, cmd.args.compressedImage.h, 0,
+ cmd.args.compressedImage.size, cmd.args.compressedImage.data);
+ break;
+ case QGles2CommandBuffer::Command::CompressedSubImage:
+ f->glBindTexture(cmd.args.compressedSubImage.target, cmd.args.compressedSubImage.texture);
+ f->glCompressedTexSubImage2D(cmd.args.compressedSubImage.faceTarget, cmd.args.compressedSubImage.level,
+ cmd.args.compressedSubImage.dx, cmd.args.compressedSubImage.dy,
+ cmd.args.compressedSubImage.w, cmd.args.compressedSubImage.h,
+ cmd.args.compressedSubImage.glintformat,
+ cmd.args.compressedSubImage.size, cmd.args.compressedSubImage.data);
+ break;
+ case QGles2CommandBuffer::Command::BlitFromRenderbuffer:
+ {
+ GLuint fbo[2];
+ f->glGenFramebuffers(2, fbo);
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, cmd.args.blitFromRb.renderbuffer);
+ f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
+
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target,
+ cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel);
+ f->glBlitFramebuffer(0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
+ 0, 0, cmd.args.blitFromRb.w, cmd.args.blitFromRb.h,
+ GL_COLOR_BUFFER_BIT,
+ GL_LINEAR);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ }
+ break;
+ case QGles2CommandBuffer::Command::GenMip:
+ f->glBindTexture(cmd.args.genMip.target, cmd.args.genMip.texture);
+ f->glGenerateMipmap(cmd.args.genMip.target);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void QRhiGles2::executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps)
+{
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+
+ // No state tracking logic as of now. Could introduce something to reduce
+ // the number of gl* calls (when using and changing between multiple
+ // pipelines), but then begin/endExternal() should invalidate the cached
+ // state as appropriate.
+
+ if (psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
+ f->glEnable(GL_SCISSOR_TEST);
+ else
+ f->glDisable(GL_SCISSOR_TEST);
+ if (psD->m_cullMode == QRhiGraphicsPipeline::None) {
+ f->glDisable(GL_CULL_FACE);
+ } else {
+ f->glEnable(GL_CULL_FACE);
+ f->glCullFace(toGlCullMode(psD->m_cullMode));
+ }
+ f->glFrontFace(toGlFrontFace(psD->m_frontFace));
+ if (!psD->m_targetBlends.isEmpty()) {
+ const QRhiGraphicsPipeline::TargetBlend &blend(psD->m_targetBlends.first()); // no MRT
+ GLboolean wr = blend.colorWrite.testFlag(QRhiGraphicsPipeline::R);
+ GLboolean wg = blend.colorWrite.testFlag(QRhiGraphicsPipeline::G);
+ GLboolean wb = blend.colorWrite.testFlag(QRhiGraphicsPipeline::B);
+ GLboolean wa = blend.colorWrite.testFlag(QRhiGraphicsPipeline::A);
+ f->glColorMask(wr, wg, wb, wa);
+ if (blend.enable) {
+ f->glEnable(GL_BLEND);
+ f->glBlendFuncSeparate(toGlBlendFactor(blend.srcColor),
+ toGlBlendFactor(blend.dstColor),
+ toGlBlendFactor(blend.srcAlpha),
+ toGlBlendFactor(blend.dstAlpha));
+ f->glBlendEquationSeparate(toGlBlendOp(blend.opColor), toGlBlendOp(blend.opAlpha));
+ } else {
+ f->glDisable(GL_BLEND);
+ }
+ } else {
+ f->glDisable(GL_BLEND);
+ }
+ if (psD->m_depthTest)
+ f->glEnable(GL_DEPTH_TEST);
+ else
+ f->glDisable(GL_DEPTH_TEST);
+ if (psD->m_depthWrite)
+ f->glDepthMask(GL_TRUE);
+ else
+ f->glDepthMask(GL_FALSE);
+ f->glDepthFunc(toGlCompareOp(psD->m_depthOp));
+ if (psD->m_stencilTest) {
+ f->glEnable(GL_STENCIL_TEST);
+ f->glStencilFuncSeparate(GL_FRONT, toGlCompareOp(psD->m_stencilFront.compareOp), 0, psD->m_stencilReadMask);
+ f->glStencilOpSeparate(GL_FRONT,
+ toGlStencilOp(psD->m_stencilFront.failOp),
+ toGlStencilOp(psD->m_stencilFront.depthFailOp),
+ toGlStencilOp(psD->m_stencilFront.passOp));
+ f->glStencilMaskSeparate(GL_FRONT, psD->m_stencilWriteMask);
+ f->glStencilFuncSeparate(GL_BACK, toGlCompareOp(psD->m_stencilBack.compareOp), 0, psD->m_stencilReadMask);
+ f->glStencilOpSeparate(GL_BACK,
+ toGlStencilOp(psD->m_stencilBack.failOp),
+ toGlStencilOp(psD->m_stencilBack.depthFailOp),
+ toGlStencilOp(psD->m_stencilBack.passOp));
+ f->glStencilMaskSeparate(GL_BACK, psD->m_stencilWriteMask);
+ } else {
+ f->glDisable(GL_STENCIL_TEST);
+ }
+
+ if (psD->topology() == QRhiGraphicsPipeline::Lines || psD->topology() == QRhiGraphicsPipeline::LineStrip)
+ f->glLineWidth(psD->m_lineWidth);
+
+ f->glUseProgram(psD->program);
+}
+
+void QRhiGles2::bindShaderResources(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb,
+ const uint *dynOfsPairs, int dynOfsCount)
+{
+ QGles2GraphicsPipeline *psD = QRHI_RES(QGles2GraphicsPipeline, ps);
+ QGles2ShaderResourceBindings *srbD = QRHI_RES(QGles2ShaderResourceBindings, srb);
+ int texUnit = 0;
+
+ for (int i = 0, ie = srbD->m_bindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->m_bindings[i]);
+
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ int viewOffset = b->u.ubuf.offset;
+ if (dynOfsCount) {
+ for (int j = 0; j < dynOfsCount; ++j) {
+ if (dynOfsPairs[2 * j] == uint(b->binding)) {
+ viewOffset = dynOfsPairs[2 * j + 1];
+ break;
+ }
+ }
+ }
+ QGles2Buffer *bufD = QRHI_RES(QGles2Buffer, b->u.ubuf.buf);
+ const QByteArray bufView = QByteArray::fromRawData(bufD->ubuf.constData() + viewOffset,
+ b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size);
+ for (QGles2GraphicsPipeline::Uniform &uniform : psD->uniforms) {
+ if (uniform.binding == b->binding) {
+ // in a uniform buffer everything is at least 4 byte aligned
+ // so this should not cause unaligned reads
+ const void *src = bufView.constData() + uniform.offset;
+
+ switch (uniform.type) {
+ case QShaderDescription::Float:
+ f->glUniform1f(uniform.glslLocation, *reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec2:
+ f->glUniform2fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec3:
+ f->glUniform3fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Vec4:
+ f->glUniform4fv(uniform.glslLocation, 1, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat2:
+ f->glUniformMatrix2fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat3:
+ f->glUniformMatrix3fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Mat4:
+ f->glUniformMatrix4fv(uniform.glslLocation, 1, GL_FALSE, reinterpret_cast<const float *>(src));
+ break;
+ case QShaderDescription::Int:
+ f->glUniform1i(uniform.glslLocation, *reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int2:
+ f->glUniform2iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int3:
+ f->glUniform3iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ case QShaderDescription::Int4:
+ f->glUniform4iv(uniform.glslLocation, 1, reinterpret_cast<const qint32 *>(src));
+ break;
+ // ### more types
+ default:
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, b->u.stex.tex);
+ QGles2Sampler *samplerD = QRHI_RES(QGles2Sampler, b->u.stex.sampler);
+
+ for (QGles2GraphicsPipeline::Sampler &sampler : psD->samplers) {
+ if (sampler.binding == b->binding) {
+ f->glActiveTexture(GL_TEXTURE0 + texUnit);
+ f->glBindTexture(texD->target, texD->texture);
+
+ if (texD->samplerState != samplerD->d) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_MIN_FILTER, samplerD->d.glminfilter);
+ f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, samplerD->d.glmagfilter);
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, samplerD->d.glwraps);
+ f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, samplerD->d.glwrapt);
+ // 3D textures not supported by GLES 2.0 or by us atm...
+ //f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, samplerD->d.glwrapr);
+ if (samplerD->d.gltexcomparefunc != GL_NEVER) {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_FUNC, samplerD->d.gltexcomparefunc);
+ } else {
+ f->glTexParameteri(texD->target, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+ }
+ texD->samplerState = samplerD->d;
+ }
+
+ f->glUniform1i(sampler.glslLocation, texUnit);
+ ++texUnit;
+ }
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (texUnit > 1)
+ f->glActiveTexture(GL_TEXTURE0);
+}
+
+void QRhiGles2::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(QRHI_RES(QGles2CommandBuffer, cb)->recordingPass == QGles2CommandBuffer::NoPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+QGles2RenderTargetData *QRhiGles2::enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
+ bool *wantsColorClear, bool *wantsDsClear)
+{
+ QGles2RenderTargetData *rtD = nullptr;
+ QGles2CommandBuffer::Command fbCmd;
+ fbCmd.cmd = QGles2CommandBuffer::Command::BindFramebuffer;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = &QRHI_RES(QGles2ReferenceRenderTarget, rt)->d;
+ if (wantsColorClear)
+ *wantsColorClear = true;
+ if (wantsDsClear)
+ *wantsDsClear = true;
+ fbCmd.args.bindFramebuffer.fbo = 0;
+ fbCmd.args.bindFramebuffer.colorAttCount = 1;
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, rt);
+ rtD = &rtTex->d;
+ if (wantsColorClear)
+ *wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+ if (wantsDsClear)
+ *wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+ fbCmd.args.bindFramebuffer.fbo = rtTex->framebuffer;
+ fbCmd.args.bindFramebuffer.colorAttCount = rtD->colorAttCount;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ fbCmd.args.bindFramebuffer.srgb = rtD->srgbUpdateAndBlend;
+ cbD->commands.append(fbCmd);
+ return rtD;
+}
+
+void QRhiGles2::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ bool wantsColorClear, wantsDsClear;
+ QGles2RenderTargetData *rtD = enqueueBindFramebuffer(rt, cbD, &wantsColorClear, &wantsDsClear);
+
+ QGles2CommandBuffer::Command clearCmd;
+ clearCmd.cmd = QGles2CommandBuffer::Command::Clear;
+ clearCmd.args.clear.mask = 0;
+ if (rtD->colorAttCount && wantsColorClear)
+ clearCmd.args.clear.mask |= GL_COLOR_BUFFER_BIT;
+ if (rtD->dsAttCount && wantsDsClear)
+ clearCmd.args.clear.mask |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ clearCmd.args.clear.c[0] = colorClearValue.redF();
+ clearCmd.args.clear.c[1] = colorClearValue.greenF();
+ clearCmd.args.clear.c[2] = colorClearValue.blueF();
+ clearCmd.args.clear.c[3] = colorClearValue.alphaF();
+ clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
+ clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
+ cbD->commands.append(clearCmd);
+
+ cbD->recordingPass = QGles2CommandBuffer::RenderPass;
+ cbD->currentTarget = rt;
+}
+
+void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::RenderPass);
+
+ if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+ QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget);
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ if (!colorAttachments.isEmpty()) {
+ // handle only 1 color attachment and only (msaa) renderbuffer
+ const QRhiColorAttachment &colorAtt(colorAttachments[0]);
+ if (colorAtt.resolveTexture()) {
+ Q_ASSERT(colorAtt.renderBuffer());
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, colorAtt.renderBuffer());
+ const QSize size = colorAtt.resolveTexture()->pixelSize();
+ if (rbD->pixelSize() != size) {
+ qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match",
+ rbD->pixelSize().width(), rbD->pixelSize().height(), size.width(), size.height());
+ }
+ QGles2CommandBuffer::Command cmd;
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
+ cmd.args.blitFromRb.renderbuffer = rbD->renderbuffer;
+ cmd.args.blitFromRb.w = size.width();
+ cmd.args.blitFromRb.h = size.height();
+ QGles2Texture *colorTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
+ const GLenum faceTargetBase = colorTexD->m_flags.testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X
+ : colorTexD->target;
+ cmd.args.blitFromRb.target = faceTargetBase + colorAtt.resolveLayer();
+ cmd.args.blitFromRb.texture = colorTexD->texture;
+ cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel();
+ cbD->commands.append(cmd);
+ }
+ }
+ }
+
+ cbD->recordingPass = QGles2CommandBuffer::NoPass;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiGles2::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ cbD->recordingPass = QGles2CommandBuffer::ComputePass;
+}
+
+void QRhiGles2::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QGles2CommandBuffer::ComputePass);
+
+ cbD->recordingPass = QGles2CommandBuffer::NoPass;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiGles2::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(ps);
+}
+
+void QRhiGles2::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+ Q_UNUSED(z);
+}
+
+QGles2Buffer::QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QGles2Buffer::~QGles2Buffer()
+{
+ release();
+}
+
+void QGles2Buffer::release()
+{
+ if (!buffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Buffer;
+
+ e.buffer.buffer = buffer;
+
+ buffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2Buffer::build()
+{
+ if (buffer)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+
+ if (m_usage.testFlag(QRhiBuffer::UniformBuffer)) {
+ if (m_usage.testFlag(QRhiBuffer::VertexBuffer) || m_usage.testFlag(QRhiBuffer::IndexBuffer)) {
+ qWarning("Uniform buffer: multiple usages specified, this is not supported by the OpenGL backend");
+ return false;
+ }
+ ubuf.resize(m_size);
+ QRHI_PROF_F(newBuffer(this, m_size, 0, 1));
+ return true;
+ }
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ if (m_usage.testFlag(QRhiBuffer::VertexBuffer)) {
+ if (m_usage.testFlag(QRhiBuffer::IndexBuffer)) {
+ qWarning("Vertex buffer: multiple usages specified, this is not supported by the OpenGL backend");
+ return false;
+ }
+ target = GL_ARRAY_BUFFER;
+ }
+ if (m_usage.testFlag(QRhiBuffer::IndexBuffer))
+ target = GL_ELEMENT_ARRAY_BUFFER;
+
+ rhiD->f->glGenBuffers(1, &buffer);
+ rhiD->f->glBindBuffer(target, buffer);
+ rhiD->f->glBufferData(target, m_size, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+
+ QRHI_PROF_F(newBuffer(this, m_size, 1, 0));
+ rhiD->registerResource(this);
+ return true;
+}
+
+QGles2RenderBuffer::QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QGles2RenderBuffer::~QGles2RenderBuffer()
+{
+ release();
+}
+
+void QGles2RenderBuffer::release()
+{
+ if (!renderbuffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::RenderBuffer;
+
+ e.renderbuffer.renderbuffer = renderbuffer;
+ e.renderbuffer.renderbuffer2 = stencilRenderbuffer;
+
+ renderbuffer = 0;
+ stencilRenderbuffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2RenderBuffer::build()
+{
+ if (renderbuffer)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ if (m_flags.testFlag(UsedWithSwapChainOnly)) {
+ if (m_type == DepthStencil) {
+ QRHI_PROF_F(newRenderBuffer(this, false, true, samples));
+ return true;
+ }
+
+ qWarning("RenderBuffer: UsedWithSwapChainOnly is meaningless in combination with Color");
+ }
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ rhiD->f->glGenRenderbuffers(1, &renderbuffer);
+ rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+
+ const QSize size = rhiD->safeTextureSize(m_pixelSize);
+
+ switch (m_type) {
+ case QRhiRenderBuffer::DepthStencil:
+ if (rhiD->caps.msaaRenderBuffer && samples > 1) {
+ const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8;
+ rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, storage,
+ size.width(), size.height());
+ stencilRenderbuffer = 0;
+ } else if (rhiD->caps.packedDepthStencil || rhiD->caps.needsDepthStencilCombinedAttach) {
+ const GLenum storage = rhiD->caps.needsDepthStencilCombinedAttach ? GL_DEPTH_STENCIL : GL_DEPTH24_STENCIL8;
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, storage,
+ size.width(), size.height());
+ stencilRenderbuffer = 0;
+ } else {
+ GLenum depthStorage = GL_DEPTH_COMPONENT;
+ if (rhiD->caps.gles) {
+ if (rhiD->caps.depth24)
+ depthStorage = GL_DEPTH_COMPONENT24;
+ else
+ depthStorage = GL_DEPTH_COMPONENT16; // plain ES 2.0 only has this
+ }
+ const GLenum stencilStorage = rhiD->caps.gles ? GL_STENCIL_INDEX8 : GL_STENCIL_INDEX;
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, depthStorage,
+ size.width(), size.height());
+ rhiD->f->glGenRenderbuffers(1, &stencilRenderbuffer);
+ rhiD->f->glBindRenderbuffer(GL_RENDERBUFFER, stencilRenderbuffer);
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, stencilStorage,
+ size.width(), size.height());
+ }
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ break;
+ case QRhiRenderBuffer::Color:
+ if (rhiD->caps.msaaRenderBuffer && samples > 1)
+ rhiD->f->glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8,
+ size.width(), size.height());
+ else
+ rhiD->f->glRenderbufferStorage(GL_RENDERBUFFER, rhiD->caps.rgba8Format ? GL_RGBA8 : GL_RGBA4,
+ size.width(), size.height());
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QGles2RenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QGles2Texture::QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+}
+
+QGles2Texture::~QGles2Texture()
+{
+ release();
+}
+
+void QGles2Texture::release()
+{
+ if (!texture)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Texture;
+
+ e.texture.texture = texture;
+
+ texture = 0;
+ specified = false;
+ nativeHandlesStruct.texture = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ if (owns)
+ rhiD->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2Texture::prepareBuild(QSize *adjustedSize)
+{
+ if (texture)
+ release();
+
+ QRHI_RES_RHI(QRhiGles2);
+ if (!rhiD->ensureContext())
+ return false;
+
+ const QSize size = rhiD->safeTextureSize(m_pixelSize);
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ const bool isCompressed = rhiD->isCompressedFormat(m_format);
+
+ target = isCube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ gltype = GL_UNSIGNED_BYTE;
+
+ if (isCompressed) {
+ glintformat = toGlCompressedTextureFormat(m_format, m_flags);
+ if (!glintformat) {
+ qWarning("Compressed format %d not mappable to GL compressed format", m_format);
+ return false;
+ }
+ glformat = GL_RGBA;
+ } else {
+ switch (m_format) {
+ case QRhiTexture::RGBA8:
+ glintformat = GL_RGBA;
+ glformat = GL_RGBA;
+ break;
+ case QRhiTexture::BGRA8:
+ glintformat = rhiD->caps.bgraInternalFormat ? GL_BGRA : GL_RGBA;
+ glformat = GL_BGRA;
+ break;
+ case QRhiTexture::R16:
+ glintformat = GL_R16;
+ glformat = GL_RED;
+ gltype = GL_UNSIGNED_SHORT;
+ break;
+ case QRhiTexture::R8:
+ glintformat = GL_R8;
+ glformat = GL_RED;
+ break;
+ case QRhiTexture::RED_OR_ALPHA8:
+ glintformat = rhiD->caps.coreProfile ? GL_R8 : GL_ALPHA;
+ glformat = rhiD->caps.coreProfile ? GL_RED : GL_ALPHA;
+ break;
+ case QRhiTexture::RGBA16F:
+ glintformat = GL_RGBA16F;
+ glformat = GL_RGBA;
+ gltype = GL_HALF_FLOAT;
+ break;
+ case QRhiTexture::RGBA32F:
+ glintformat = GL_RGBA32F;
+ glformat = GL_RGBA;
+ gltype = GL_FLOAT;
+ break;
+ case QRhiTexture::D16:
+ glintformat = GL_DEPTH_COMPONENT16;
+ glformat = GL_DEPTH_COMPONENT;
+ gltype = GL_UNSIGNED_SHORT;
+ break;
+ case QRhiTexture::D32F:
+ glintformat = GL_DEPTH_COMPONENT32F;
+ glformat = GL_DEPTH_COMPONENT;
+ gltype = GL_FLOAT;
+ break;
+ default:
+ Q_UNREACHABLE();
+ glintformat = GL_RGBA;
+ glformat = GL_RGBA;
+ break;
+ }
+ }
+
+ samplerState = QGles2SamplerData();
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QGles2Texture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->f->glGenTextures(1, &texture);
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ const bool isCompressed = rhiD->isCompressedFormat(m_format);
+ if (!isCompressed) {
+ rhiD->f->glBindTexture(target, texture);
+ if (hasMipMaps || isCube) {
+ const GLenum faceTargetBase = isCube ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
+ for (int layer = 0, layerCount = isCube ? 6 : 1; layer != layerCount; ++layer) {
+ for (int level = 0; level != mipLevelCount; ++level) {
+ const QSize mipSize = rhiD->q->sizeForMipLevel(level, size);
+ rhiD->f->glTexImage2D(faceTargetBase + layer, level, glintformat,
+ mipSize.width(), mipSize.height(), 0,
+ glformat, gltype, nullptr);
+ }
+ }
+ } else {
+ rhiD->f->glTexImage2D(target, 0, glintformat, size.width(), size.height(), 0, glformat, gltype, nullptr);
+ }
+ specified = true;
+ } else {
+ // Cannot use glCompressedTexImage2D without valid data, so defer.
+ // Compressed textures will not be used as render targets so this is
+ // not an issue.
+ specified = false;
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
+
+ owns = true;
+ nativeHandlesStruct.texture = texture;
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QGles2Texture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiGles2TextureNativeHandles *h = static_cast<const QRhiGles2TextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ texture = h->texture;
+ specified = true;
+
+ QRHI_RES_RHI(QRhiGles2);
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, 1));
+
+ owns = false;
+ nativeHandlesStruct.texture = texture;
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QGles2Texture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QGles2Sampler::QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QGles2Sampler::~QGles2Sampler()
+{
+ release();
+}
+
+void QGles2Sampler::release()
+{
+ // nothing to do here
+}
+
+bool QGles2Sampler::build()
+{
+ d.glminfilter = toGlMinFilter(m_minFilter, m_mipmapMode);
+ d.glmagfilter = toGlMagFilter(m_magFilter);
+ d.glwraps = toGlWrapMode(m_addressU);
+ d.glwrapt = toGlWrapMode(m_addressV);
+ d.glwrapr = toGlWrapMode(m_addressW);
+ d.gltexcomparefunc = toGlTextureCompareFunc(m_compareOp);
+
+ generation += 1;
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here
+QGles2RenderPassDescriptor::QGles2RenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QGles2RenderPassDescriptor::~QGles2RenderPassDescriptor()
+{
+ release();
+}
+
+void QGles2RenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QGles2ReferenceRenderTarget::QGles2ReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QGles2ReferenceRenderTarget::~QGles2ReferenceRenderTarget()
+{
+ release();
+}
+
+void QGles2ReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QGles2ReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QGles2ReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QGles2ReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QGles2TextureRenderTarget::QGles2TextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+}
+
+QGles2TextureRenderTarget::~QGles2TextureRenderTarget()
+{
+ release();
+}
+
+void QGles2TextureRenderTarget::release()
+{
+ if (!framebuffer)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget;
+
+ e.textureRenderTarget.framebuffer = framebuffer;
+
+ framebuffer = 0;
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QGles2TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QGles2RenderPassDescriptor(m_rhi);
+}
+
+bool QGles2TextureRenderTarget::build()
+{
+ QRHI_RES_RHI(QRhiGles2);
+
+ if (framebuffer)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ if (colorAttachments.count() > rhiD->caps.maxDrawBuffers)
+ qWarning("QGles2TextureRenderTarget: Too many color attachments (%d, max is %d)",
+ colorAttachments.count(), rhiD->caps.maxDrawBuffers);
+ if (m_desc.depthTexture() && !rhiD->caps.depthTexture)
+ qWarning("QGles2TextureRenderTarget: Depth texture is not supported and will be ignored");
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ rhiD->f->glGenFramebuffers(1, &framebuffer);
+ rhiD->f->glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ const QRhiColorAttachment &colorAtt(colorAttachments[i]);
+ QRhiTexture *texture = colorAtt.texture();
+ QRhiRenderBuffer *renderBuffer = colorAtt.renderBuffer();
+ Q_ASSERT(texture || renderBuffer);
+ if (texture) {
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
+ Q_ASSERT(texD->texture && texD->specified);
+ const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, faceTargetBase + colorAtt.layer(), texD->texture, colorAtt.level());
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = 1;
+ }
+ } else if (renderBuffer) {
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer);
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, rbD->renderbuffer);
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->samples;
+ }
+ }
+ }
+
+ if (hasDepthStencil) {
+ if (m_desc.depthStencilBuffer()) {
+ QGles2RenderBuffer *depthRbD = QRHI_RES(QGles2RenderBuffer, m_desc.depthStencilBuffer());
+ if (rhiD->caps.needsDepthStencilCombinedAttach) {
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+ depthRbD->renderbuffer);
+ } else {
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
+ depthRbD->renderbuffer);
+ if (depthRbD->stencilRenderbuffer)
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+ depthRbD->stencilRenderbuffer);
+ else // packed
+ rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
+ depthRbD->renderbuffer);
+ }
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthRbD->pixelSize();
+ d.sampleCount = depthRbD->samples;
+ }
+ } else {
+ QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexD->texture, 0);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = 1;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ d.dpr = 1;
+ d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
+
+ GLenum status = rhiD->f->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_NO_ERROR && status != GL_FRAMEBUFFER_COMPLETE) {
+ qWarning("Framebuffer incomplete: 0x%x", status);
+ return false;
+ }
+
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QGles2TextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QGles2TextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QGles2TextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QGles2ShaderResourceBindings::QGles2ShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QGles2ShaderResourceBindings::~QGles2ShaderResourceBindings()
+{
+ release();
+}
+
+void QGles2ShaderResourceBindings::release()
+{
+ // nothing to do here
+}
+
+bool QGles2ShaderResourceBindings::build()
+{
+ generation += 1;
+ return true;
+}
+
+QGles2GraphicsPipeline::QGles2GraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QGles2GraphicsPipeline::~QGles2GraphicsPipeline()
+{
+ release();
+}
+
+void QGles2GraphicsPipeline::release()
+{
+ if (!program)
+ return;
+
+ QRhiGles2::DeferredReleaseEntry e;
+ e.type = QRhiGles2::DeferredReleaseEntry::Pipeline;
+
+ e.pipeline.program = program;
+
+ program = 0;
+ uniforms.clear();
+ samplers.clear();
+
+ QRHI_RES_RHI(QRhiGles2);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QGles2GraphicsPipeline::build()
+{
+ QRHI_RES_RHI(QRhiGles2);
+
+ if (program)
+ release();
+
+ if (!rhiD->ensureContext())
+ return false;
+
+ drawMode = toGlTopology(m_topology);
+
+ program = rhiD->f->glCreateProgram();
+
+ int sourceVer = 0;
+ for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ const bool isVertex = shaderStage.type() == QRhiShaderStage::Vertex;
+ const bool isFragment = shaderStage.type() == QRhiShaderStage::Fragment;
+ if (!isVertex && !isFragment)
+ continue;
+
+ GLuint shader = rhiD->f->glCreateShader(isVertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER);
+ const QShader bakedShader = shaderStage.shader();
+ QVector<int> versionsToTry;
+ QByteArray source;
+ if (rhiD->caps.gles) {
+ if (rhiD->caps.ctxMajor > 3 || (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor >= 2)) {
+ versionsToTry << 320 << 310 << 300 << 100;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 1) {
+ versionsToTry << 310 << 300 << 100;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 0) {
+ versionsToTry << 300 << 100;
+ } else {
+ versionsToTry << 100;
+ }
+ for (int v : versionsToTry) {
+ QShaderVersion ver(v, QShaderVersion::GlslEs);
+ source = bakedShader.shader({ QShader::GlslShader, ver, shaderStage.shaderVariant() }).shader();
+ if (!source.isEmpty()) {
+ sourceVer = v;
+ break;
+ }
+ }
+ } else {
+ if (rhiD->caps.ctxMajor > 4 || (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor >= 6)) {
+ versionsToTry << 460 << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 5) {
+ versionsToTry << 450 << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 4) {
+ versionsToTry << 440 << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 3) {
+ versionsToTry << 430 << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 2) {
+ versionsToTry << 420 << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 1) {
+ versionsToTry << 410 << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 4 && rhiD->caps.ctxMinor == 0) {
+ versionsToTry << 400 << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 3) {
+ versionsToTry << 330 << 150;
+ } else if (rhiD->caps.ctxMajor == 3 && rhiD->caps.ctxMinor == 2) {
+ versionsToTry << 150;
+ }
+ if (!rhiD->caps.coreProfile)
+ versionsToTry << 120;
+ for (int v : versionsToTry) {
+ source = bakedShader.shader({ QShader::GlslShader, v, shaderStage.shaderVariant() }).shader();
+ if (!source.isEmpty()) {
+ sourceVer = v;
+ break;
+ }
+ }
+ }
+ if (source.isEmpty()) {
+ qWarning() << "No GLSL shader code found (versions tried: " << versionsToTry
+ << ") in baked shader" << bakedShader;
+ return false;
+ }
+
+ const char *srcStr = source.constData();
+ const GLint srcLength = source.count();
+ rhiD->f->glShaderSource(shader, 1, &srcStr, &srcLength);
+ rhiD->f->glCompileShader(shader);
+ GLint compiled = 0;
+ rhiD->f->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ GLint infoLogLength = 0;
+ rhiD->f->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+ QByteArray log;
+ if (infoLogLength > 1) {
+ GLsizei length = 0;
+ log.resize(infoLogLength);
+ rhiD->f->glGetShaderInfoLog(shader, infoLogLength, &length, log.data());
+ }
+ qWarning("Failed to compile shader: %s\nSource was:\n%s", log.constData(), source.constData());
+ return false;
+ }
+
+ rhiD->f->glAttachShader(program, shader);
+ rhiD->f->glDeleteShader(shader);
+
+ if (isVertex)
+ vsDesc = bakedShader.description();
+ else
+ fsDesc = bakedShader.description();
+ }
+
+ // not very useful atm since we assume the qsb-generated GLSL shaders never use uniform blocks
+ canUseUniformBuffers = rhiD->caps.uniformBuffers && sourceVer >= 140;
+
+ for (auto inVar : vsDesc.inputVariables()) {
+ const QByteArray name = inVar.name.toUtf8();
+ rhiD->f->glBindAttribLocation(program, inVar.location, name.constData());
+ }
+
+ rhiD->f->glLinkProgram(program);
+ GLint linked = 0;
+ rhiD->f->glGetProgramiv(program, GL_LINK_STATUS, &linked);
+ if (!linked) {
+ GLint infoLogLength = 0;
+ rhiD->f->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+ QByteArray log;
+ if (infoLogLength > 1) {
+ GLsizei length = 0;
+ log.resize(infoLogLength);
+ rhiD->f->glGetProgramInfoLog(program, infoLogLength, &length, log.data());
+ }
+ qWarning("Failed to link shader program: %s", log.constData());
+ return false;
+ }
+
+ auto lookupUniforms = [this, rhiD](const QShaderDescription::UniformBlock &ub) {
+ const QByteArray prefix = ub.structName.toUtf8() + '.';
+ for (const QShaderDescription::BlockVariable &blockMember : ub.members) {
+ // ### no array support for now
+ Uniform uniform;
+ uniform.type = blockMember.type;
+ const QByteArray name = prefix + blockMember.name.toUtf8();
+ uniform.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData());
+ if (uniform.glslLocation >= 0) {
+ uniform.binding = ub.binding;
+ uniform.offset = blockMember.offset;
+ uniform.size = blockMember.size;
+ uniforms.append(uniform);
+ }
+ }
+ };
+
+ for (const QShaderDescription::UniformBlock &ub : vsDesc.uniformBlocks())
+ lookupUniforms(ub);
+
+ for (const QShaderDescription::UniformBlock &ub : fsDesc.uniformBlocks())
+ lookupUniforms(ub);
+
+ auto lookupSamplers = [this, rhiD](const QShaderDescription::InOutVariable &v) {
+ Sampler sampler;
+ const QByteArray name = v.name.toUtf8();
+ sampler.glslLocation = rhiD->f->glGetUniformLocation(program, name.constData());
+ if (sampler.glslLocation >= 0) {
+ sampler.binding = v.binding;
+ samplers.append(sampler);
+ }
+ };
+
+ for (const QShaderDescription::InOutVariable &v : vsDesc.combinedImageSamplers())
+ lookupSamplers(v);
+
+ for (const QShaderDescription::InOutVariable &v : fsDesc.combinedImageSamplers())
+ lookupSamplers(v);
+
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QGles2ComputePipeline::QGles2ComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi)
+{
+}
+
+QGles2ComputePipeline::~QGles2ComputePipeline()
+{
+ release();
+}
+
+void QGles2ComputePipeline::release()
+{
+}
+
+bool QGles2ComputePipeline::build()
+{
+ return false;
+}
+
+QGles2CommandBuffer::QGles2CommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QGles2CommandBuffer::~QGles2CommandBuffer()
+{
+ release();
+}
+
+void QGles2CommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QGles2SwapChain::QGles2SwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+}
+
+QGles2SwapChain::~QGles2SwapChain()
+{
+ release();
+}
+
+void QGles2SwapChain::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+}
+
+QRhiCommandBuffer *QGles2SwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QGles2SwapChain::surfacePixelSize()
+{
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
+}
+
+QRhiRenderPassDescriptor *QGles2SwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QGles2RenderPassDescriptor(m_rhi);
+}
+
+bool QGles2SwapChain::buildOrResize()
+{
+ surface = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ rt.d.rp = QRHI_RES(QGles2RenderPassDescriptor, m_renderPassDesc);
+ rt.d.pixelSize = pixelSize;
+ rt.d.dpr = m_window->devicePixelRatio();
+ rt.d.sampleCount = qBound(1, m_sampleCount, 64);
+ rt.d.colorAttCount = 1;
+ rt.d.dsAttCount = m_depthStencil ? 1 : 0;
+ rt.d.srgbUpdateAndBlend = m_flags.testFlag(QRhiSwapChain::sRGB);
+
+ frameCount = 0;
+
+ QRHI_PROF;
+ // make something up
+ QRHI_PROF_F(resizeSwapChain(this, 2, m_sampleCount > 1 ? 2 : 0, m_sampleCount));
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
new file mode 100644
index 0000000000..7f7c8b4c40
--- /dev/null
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIGLES2_H
+#define QRHIGLES2_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+#include <QtGui/qsurfaceformat.h>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLContext;
+class QOffscreenSurface;
+class QWindow;
+
+struct Q_GUI_EXPORT QRhiGles2InitParams : public QRhiInitParams
+{
+ QRhiGles2InitParams();
+
+ QSurfaceFormat format;
+ QOffscreenSurface *fallbackSurface = nullptr;
+ QWindow *window = nullptr;
+
+ static QOffscreenSurface *newFallbackSurface(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat());
+ static QSurfaceFormat adjustedFormat(const QSurfaceFormat &format = QSurfaceFormat::defaultFormat());
+};
+
+struct Q_GUI_EXPORT QRhiGles2NativeHandles : public QRhiNativeHandles
+{
+ QOpenGLContext *context = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiGles2TextureNativeHandles : public QRhiNativeHandles
+{
+ uint texture = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhigles2_p_p.h b/src/gui/rhi/qrhigles2_p_p.h
new file mode 100644
index 0000000000..d6682977ff
--- /dev/null
+++ b/src/gui/rhi/qrhigles2_p_p.h
@@ -0,0 +1,732 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIGLES2_P_H
+#define QRHIGLES2_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhigles2_p.h"
+#include "qrhi_p_p.h"
+#include "qshaderdescription_p.h"
+#include <qopengl.h>
+#include <QSurface>
+
+QT_BEGIN_NAMESPACE
+
+class QOpenGLExtensions;
+
+struct QGles2Buffer : public QRhiBuffer
+{
+ QGles2Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QGles2Buffer();
+ void release() override;
+ bool build() override;
+
+ GLuint buffer = 0;
+ GLenum target;
+ QByteArray ubuf;
+ friend class QRhiGles2;
+};
+
+struct QGles2RenderBuffer : public QRhiRenderBuffer
+{
+ QGles2RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QGles2RenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ GLuint renderbuffer = 0;
+ GLuint stencilRenderbuffer = 0; // when packed depth-stencil not supported
+ int samples;
+ friend class QRhiGles2;
+};
+
+struct QGles2SamplerData
+{
+ GLenum glminfilter = 0;
+ GLenum glmagfilter = 0;
+ GLenum glwraps = 0;
+ GLenum glwrapt = 0;
+ GLenum glwrapr = 0;
+ GLenum gltexcomparefunc = 0;
+};
+
+inline bool operator==(const QGles2SamplerData &a, const QGles2SamplerData &b)
+{
+ return a.glminfilter == b.glminfilter
+ && a.glmagfilter == b.glmagfilter
+ && a.glwraps == b.glwraps
+ && a.glwrapt == b.glwrapt
+ && a.glwrapr == b.glwrapr
+ && a.gltexcomparefunc == b.gltexcomparefunc;
+}
+
+inline bool operator!=(const QGles2SamplerData &a, const QGles2SamplerData &b)
+{
+ return !(a == b);
+}
+
+struct QGles2Texture : public QRhiTexture
+{
+ QGles2Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QGles2Texture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+
+ GLuint texture = 0;
+ bool owns = true;
+ GLenum target;
+ GLenum glintformat;
+ GLenum glformat;
+ GLenum gltype;
+ QGles2SamplerData samplerState;
+ bool specified = false;
+ int mipLevelCount = 0;
+ QRhiGles2TextureNativeHandles nativeHandlesStruct;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2Sampler : public QRhiSampler
+{
+ QGles2Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QGles2Sampler();
+ void release() override;
+ bool build() override;
+
+ QGles2SamplerData d;
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2RenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QGles2RenderPassDescriptor(QRhiImplementation *rhi);
+ ~QGles2RenderPassDescriptor();
+ void release() override;
+};
+
+struct QGles2RenderTargetData
+{
+ QGles2RenderTargetData(QRhiImplementation *) { }
+
+ QGles2RenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+ bool srgbUpdateAndBlend = false;
+};
+
+struct QGles2ReferenceRenderTarget : public QRhiRenderTarget
+{
+ QGles2ReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QGles2ReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QGles2RenderTargetData d;
+};
+
+struct QGles2TextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QGles2TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QGles2TextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QGles2RenderTargetData d;
+ GLuint framebuffer = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2ShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QGles2ShaderResourceBindings(QRhiImplementation *rhi);
+ ~QGles2ShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+struct QGles2GraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QGles2GraphicsPipeline(QRhiImplementation *rhi);
+ ~QGles2GraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ GLuint program = 0;
+ GLenum drawMode = GL_TRIANGLES;
+ QShaderDescription vsDesc;
+ QShaderDescription fsDesc;
+ bool canUseUniformBuffers = false;
+
+ struct Uniform {
+ QShaderDescription::VariableType type;
+ int glslLocation;
+ int binding;
+ uint offset;
+ int size;
+ };
+ QVector<Uniform> uniforms;
+
+ struct Sampler {
+ int glslLocation;
+ int binding;
+ };
+ QVector<Sampler> samplers;
+
+ uint generation = 0;
+ friend class QRhiGles2;
+};
+
+Q_DECLARE_TYPEINFO(QGles2GraphicsPipeline::Uniform, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QGles2GraphicsPipeline::Sampler, Q_MOVABLE_TYPE);
+
+struct QGles2ComputePipeline : public QRhiComputePipeline
+{
+ QGles2ComputePipeline(QRhiImplementation *rhi);
+ ~QGles2ComputePipeline();
+ void release() override;
+ bool build() override;
+};
+
+struct QGles2CommandBuffer : public QRhiCommandBuffer
+{
+ QGles2CommandBuffer(QRhiImplementation *rhi);
+ ~QGles2CommandBuffer();
+ void release() override;
+
+ struct Command {
+ enum Cmd {
+ BeginFrame,
+ EndFrame,
+ Viewport,
+ Scissor,
+ BlendConstants,
+ StencilRef,
+ BindVertexBuffer,
+ BindIndexBuffer,
+ Draw,
+ DrawIndexed,
+ BindGraphicsPipeline,
+ BindShaderResources,
+ BindFramebuffer,
+ Clear,
+ BufferData,
+ BufferSubData,
+ CopyTex,
+ ReadPixels,
+ SubImage,
+ CompressedImage,
+ CompressedSubImage,
+ BlitFromRenderbuffer,
+ GenMip
+ };
+ Cmd cmd;
+
+ static const int MAX_UBUF_BINDINGS = 32; // should be more than enough
+
+ // QRhi*/QGles2* references should be kept at minimum (so no
+ // QRhiTexture/Buffer/etc. pointers).
+ union {
+ struct {
+ float x, y, w, h;
+ float d0, d1;
+ } viewport;
+ struct {
+ int x, y, w, h;
+ } scissor;
+ struct {
+ float r, g, b, a;
+ } blendConstants;
+ struct {
+ quint32 ref;
+ QRhiGraphicsPipeline *ps;
+ } stencilRef;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ GLuint buffer;
+ quint32 offset;
+ int binding;
+ } bindVertexBuffer;
+ struct {
+ GLuint buffer;
+ quint32 offset;
+ GLenum type;
+ } bindIndexBuffer;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ quint32 vertexCount;
+ quint32 firstVertex;
+ quint32 instanceCount;
+ quint32 baseInstance;
+ } draw;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ quint32 indexCount;
+ quint32 firstIndex;
+ quint32 instanceCount;
+ quint32 baseInstance;
+ qint32 baseVertex;
+ } drawIndexed;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ } bindGraphicsPipeline;
+ struct {
+ QRhiGraphicsPipeline *ps;
+ QRhiShaderResourceBindings *srb;
+ int dynamicOffsetCount;
+ uint dynamicOffsetPairs[MAX_UBUF_BINDINGS * 2]; // binding, offsetInConstants
+ } bindShaderResources;
+ struct {
+ GLbitfield mask;
+ float c[4];
+ float d;
+ quint32 s;
+ } clear;
+ struct {
+ GLuint fbo;
+ bool srgb;
+ int colorAttCount;
+ } bindFramebuffer;
+ struct {
+ GLenum target;
+ GLuint buffer;
+ int offset;
+ int size;
+ const void *data; // must come from retainData()
+ } bufferSubData;
+ struct {
+ GLenum srcFaceTarget;
+ GLuint srcTexture;
+ int srcLevel;
+ int srcX;
+ int srcY;
+ GLenum dstTarget;
+ GLuint dstTexture;
+ GLenum dstFaceTarget;
+ int dstLevel;
+ int dstX;
+ int dstY;
+ int w;
+ int h;
+ } copyTex;
+ struct {
+ QRhiReadbackResult *result;
+ GLuint texture;
+ int w;
+ int h;
+ QRhiTexture::Format format;
+ GLenum readTarget;
+ int level;
+ } readPixels;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ int dx;
+ int dy;
+ int w;
+ int h;
+ GLenum glformat;
+ GLenum gltype;
+ int rowStartAlign;
+ const void *data; // must come from retainImage()
+ } subImage;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ GLenum glintformat;
+ int w;
+ int h;
+ int size;
+ const void *data; // must come from retainData()
+ } compressedImage;
+ struct {
+ GLenum target;
+ GLuint texture;
+ GLenum faceTarget;
+ int level;
+ int dx;
+ int dy;
+ int w;
+ int h;
+ GLenum glintformat;
+ int size;
+ const void *data; // must come from retainData()
+ } compressedSubImage;
+ struct {
+ GLuint renderbuffer;
+ int w;
+ int h;
+ GLenum target;
+ GLuint texture;
+ int dstLevel;
+ } blitFromRb;
+ struct {
+ GLenum target;
+ GLuint texture;
+ } genMip;
+ } args;
+ };
+
+ enum PassType {
+ NoPass,
+ RenderPass,
+ ComputePass
+ };
+
+ QVector<Command> commands;
+ PassType recordingPass;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentPipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentSrb;
+ uint currentSrbGeneration;
+
+ QVector<QByteArray> dataRetainPool;
+ QVector<QImage> imageRetainPool;
+
+ // relies heavily on implicit sharing (no copies of the actual data will be made)
+ const void *retainData(const QByteArray &data) {
+ dataRetainPool.append(data);
+ return dataRetainPool.constLast().constData();
+ }
+ const void *retainImage(const QImage &image) {
+ imageRetainPool.append(image);
+ return imageRetainPool.constLast().constBits();
+ }
+ void resetCommands() {
+ commands.clear();
+ dataRetainPool.clear();
+ imageRetainPool.clear();
+ }
+ void resetState() {
+ resetCommands();
+ recordingPass = NoPass;
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+ void resetCachedState() {
+ currentPipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentSrb = nullptr;
+ currentSrbGeneration = 0;
+ }
+};
+
+Q_DECLARE_TYPEINFO(QGles2CommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QGles2SwapChain : public QRhiSwapChain
+{
+ QGles2SwapChain(QRhiImplementation *rhi);
+ ~QGles2SwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ QSurface *surface = nullptr;
+ QSize pixelSize;
+ QGles2ReferenceRenderTarget rt;
+ QGles2CommandBuffer cb;
+ int frameCount = 0;
+};
+
+class QRhiGles2 : public QRhiImplementation
+{
+public:
+ QRhiGles2(QRhiGles2InitParams *params, QRhiGles2NativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiComputePipeline *createComputePipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+ void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+ void makeThreadLocalNativeContextCurrent() override;
+
+ bool ensureContext(QSurface *surface = nullptr) const;
+ void executeDeferredReleases();
+ QRhi::FrameOpResult flushCommandBuffer();
+ void enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cbD,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeCommandBuffer(QRhiCommandBuffer *cb);
+ void executeBindGraphicsPipeline(QRhiGraphicsPipeline *ps);
+ void bindShaderResources(QRhiGraphicsPipeline *ps, QRhiShaderResourceBindings *srb,
+ const uint *dynOfsPairs, int dynOfsCount);
+ QGles2RenderTargetData *enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
+ bool *wantsColorClear = nullptr, bool *wantsDsClear = nullptr);
+ int effectiveSampleCount(int sampleCount) const;
+ QSize safeTextureSize(const QSize &size) const;
+
+ QOpenGLContext *ctx = nullptr;
+ bool importedContext = false;
+ QSurfaceFormat requestedFormat;
+ QSurface *fallbackSurface = nullptr;
+ QWindow *maybeWindow = nullptr;
+ mutable bool needsMakeCurrent = false;
+ QOpenGLExtensions *f = nullptr;
+ uint vao = 0;
+ struct Caps {
+ Caps()
+ : ctxMajor(2),
+ ctxMinor(0),
+ maxTextureSize(2048),
+ maxDrawBuffers(4),
+ msaaRenderBuffer(false),
+ npotTexture(true),
+ npotTextureRepeat(true),
+ gles(false),
+ fixedIndexPrimitiveRestart(false),
+ bgraExternalFormat(false),
+ bgraInternalFormat(false),
+ r8Format(false),
+ r16Format(false),
+ floatFormats(false),
+ depthTexture(false),
+ packedDepthStencil(false),
+ needsDepthStencilCombinedAttach(false),
+ srgbCapableDefaultFramebuffer(false),
+ coreProfile(false),
+ uniformBuffers(false),
+ elementIndexUint(false),
+ depth24(false),
+ rgba8Format(false),
+ instancing(false),
+ baseVertex(false)
+ { }
+ int ctxMajor;
+ int ctxMinor;
+ int maxTextureSize;
+ int maxDrawBuffers;
+ int maxSamples;
+ // Multisample fb and blit are supported (GLES 3.0 or OpenGL 3.x). Not
+ // the same as multisample textures!
+ uint msaaRenderBuffer : 1;
+ uint npotTexture : 1;
+ uint npotTextureRepeat : 1;
+ uint gles : 1;
+ uint fixedIndexPrimitiveRestart : 1;
+ uint bgraExternalFormat : 1;
+ uint bgraInternalFormat : 1;
+ uint r8Format : 1;
+ uint r16Format : 1;
+ uint floatFormats : 1;
+ uint depthTexture : 1;
+ uint packedDepthStencil : 1;
+ uint needsDepthStencilCombinedAttach : 1;
+ uint srgbCapableDefaultFramebuffer : 1;
+ uint coreProfile : 1;
+ uint uniformBuffers : 1;
+ uint elementIndexUint : 1;
+ uint depth24 : 1;
+ uint rgba8Format : 1;
+ uint instancing : 1;
+ uint baseVertex : 1;
+ } caps;
+ QGles2SwapChain *currentSwapChain = nullptr;
+ QVector<GLint> supportedCompressedFormats;
+ mutable QVector<int> supportedSampleCountList;
+ QRhiGles2NativeHandles nativeHandlesStruct;
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Buffer,
+ Pipeline,
+ Texture,
+ RenderBuffer,
+ TextureRenderTarget
+ };
+ Type type;
+ union {
+ struct {
+ GLuint buffer;
+ } buffer;
+ struct {
+ GLuint program;
+ } pipeline;
+ struct {
+ GLuint texture;
+ } texture;
+ struct {
+ GLuint renderbuffer;
+ GLuint renderbuffer2;
+ } renderbuffer;
+ struct {
+ GLuint framebuffer;
+ } textureRenderTarget;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QGles2CommandBuffer cbWrapper;
+ } ofr;
+};
+
+Q_DECLARE_TYPEINFO(QRhiGles2::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
new file mode 100644
index 0000000000..fa537a504b
--- /dev/null
+++ b/src/gui/rhi/qrhimetal.mm
@@ -0,0 +1,3566 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhimetal_p_p.h"
+#include "qshader_p.h"
+#include "qshaderdescription_p.h"
+#include <QGuiApplication>
+#include <QWindow>
+#include <qmath.h>
+
+#ifdef Q_OS_MACOS
+#include <AppKit/AppKit.h>
+#endif
+
+#include <Metal/Metal.h>
+#include <QuartzCore/CAMetalLayer.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Metal backend. Double buffers and throttles to vsync. "Dynamic" buffers are
+ Shared (host visible) and duplicated (to help having 2 frames in flight),
+ "static" and "immutable" are Managed on macOS and Shared on iOS/tvOS.
+ Textures are Private (device local) and a host visible staging buffer is
+ used to upload data to them. Does not rely on strong objects refs from
+ command buffers but does rely on the automatic resource tracking of the
+ command encoders.
+*/
+
+#if __has_feature(objc_arc)
+#error ARC not supported
+#endif
+
+// Note: we expect everything here pass the Metal API validation when running
+// in Debug mode in XCode. Some of the issues that break validation are not
+// obvious and not visible when running outside XCode.
+//
+// An exception is the nextDrawable Called Early blah blah warning, which is
+// plain and simply false.
+
+/*!
+ \class QRhiMetalInitParams
+ \inmodule QtRhi
+ \brief Metal specific initialization parameters.
+
+ A Metal-based QRhi needs no special parameters for initialization.
+
+ \badcode
+ QRhiMetalInitParams params;
+ rhi = QRhi::create(QRhi::Metal, &params);
+ \endcode
+
+ \note Metal API validation cannot be enabled by the application. Instead,
+ run the debug build of the application in XCode. Generating a
+ \c{.xcodeproj} file via \c{qmake -spec macx-xcode} provides a convenient
+ way to enable this.
+
+ \note QRhiSwapChain can only target QWindow instances that have their
+ surface type set to QSurface::MetalSurface.
+
+ \section2 Working with existing Metal devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Metal device. This can be achieved
+ by passing a pointer to a QRhiMetalNativeHandles to QRhi::create(). The
+ device must be set to a non-null value then. Optionally, a command queue
+ object can be specified as well.
+
+ The QRhi does not take ownership of any of the external objects.
+ */
+
+/*!
+ \class QRhiMetalNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal device used by the QRhi.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual types are \c{id<MTLDevice>} and
+ \c{id<MTLCommandQueue>}.
+ */
+
+/*!
+ \class QRhiMetalTextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Metal texture object that is backing a QRhiTexture instance.
+
+ \note The class uses \c{void *} as the type since including the Objective C
+ headers is not acceptable here. The actual type is \c{id<MTLTexture>}.
+ */
+
+/*!
+ \class QRhiMetalCommandBufferNativeHandles
+ \inmodule QtRhi
+ \brief Holds the MTLCommandBuffer and MTLRenderCommandEncoder objects that are backing a QRhiCommandBuffer.
+
+ \note The command buffer object is only guaranteed to be valid while
+ recording a frame, that is, between a \l{QRhi::beginFrame()}{beginFrame()}
+ - \l{QRhi::endFrame()}{endFrame()} or
+ \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+ \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
+
+ \note The command encoder is only valid while recording a pass, that is,
+ between \l{QRhiCommandBuffer::beginPass()} -
+ \l{QRhiCommandBuffer::endPass()}.
+ */
+
+struct QRhiMetalData
+{
+ QRhiMetalData(QRhiImplementation *rhi) : ofr(rhi) { }
+
+ id<MTLDevice> dev = nil;
+ id<MTLCommandQueue> cmdQueue = nil;
+
+ MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount);
+ id<MTLLibrary> createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint);
+ id<MTLFunction> createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint);
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Buffer,
+ RenderBuffer,
+ Texture,
+ Sampler,
+ StagingBuffer
+ };
+ Type type;
+ int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
+ union {
+ struct {
+ id<MTLBuffer> buffers[QMTL_FRAMES_IN_FLIGHT];
+ } buffer;
+ struct {
+ id<MTLTexture> texture;
+ } renderbuffer;
+ struct {
+ id<MTLTexture> texture;
+ id<MTLBuffer> stagingBuffers[QMTL_FRAMES_IN_FLIGHT];
+ id<MTLTexture> views[QRhi::MAX_LEVELS];
+ } texture;
+ struct {
+ id<MTLSamplerState> samplerState;
+ } sampler;
+ struct {
+ id<MTLBuffer> buffer;
+ } stagingBuffer;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QMetalCommandBuffer cbWrapper;
+ } ofr;
+
+ struct ActiveReadback {
+ int activeFrameSlot = -1;
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ id<MTLBuffer> buf;
+ quint32 bufSize;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+
+ API_AVAILABLE(macos(10.13), ios(11.0)) MTLCaptureManager *captureMgr;
+ API_AVAILABLE(macos(10.13), ios(11.0)) id<MTLCaptureScope> captureScope = nil;
+
+ static const int TEXBUF_ALIGN = 256; // probably not accurate
+};
+
+Q_DECLARE_TYPEINFO(QRhiMetalData::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiMetalData::ActiveReadback, Q_MOVABLE_TYPE);
+
+struct QMetalBufferData
+{
+ bool managed;
+ bool slotted;
+ id<MTLBuffer> buf[QMTL_FRAMES_IN_FLIGHT];
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingUpdates[QMTL_FRAMES_IN_FLIGHT];
+};
+
+struct QMetalRenderBufferData
+{
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+};
+
+struct QMetalTextureData
+{
+ QMetalTextureData(QMetalTexture *t) : q(t) { }
+
+ QMetalTexture *q;
+ MTLPixelFormat format;
+ id<MTLTexture> tex = nil;
+ id<MTLBuffer> stagingBuf[QMTL_FRAMES_IN_FLIGHT];
+ bool owns = true;
+ id<MTLTexture> perLevelViews[QRhi::MAX_LEVELS];
+
+ id<MTLTexture> viewForLevel(int level);
+};
+
+struct QMetalSamplerData
+{
+ id<MTLSamplerState> samplerState = nil;
+};
+
+struct QMetalCommandBufferData
+{
+ id<MTLCommandBuffer> cb;
+ id<MTLRenderCommandEncoder> currentRenderPassEncoder;
+ id<MTLComputeCommandEncoder> currentComputePassEncoder;
+ MTLRenderPassDescriptor *currentPassRpDesc;
+ int currentFirstVertexBinding;
+ QRhiBatchedBindings<id<MTLBuffer> > currentVertexInputsBuffers;
+ QRhiBatchedBindings<NSUInteger> currentVertexInputOffsets;
+};
+
+struct QMetalRenderTargetData
+{
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+
+ struct ColorAtt {
+ bool needsDrawableForTex = false;
+ id<MTLTexture> tex = nil;
+ int layer = 0;
+ int level = 0;
+ bool needsDrawableForResolveTex = false;
+ id<MTLTexture> resolveTex = nil;
+ int resolveLayer = 0;
+ int resolveLevel = 0;
+ };
+
+ struct {
+ ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
+ id<MTLTexture> dsTex = nil;
+ bool hasStencil = false;
+ bool depthNeedsStore = false;
+ } fb;
+};
+
+struct QMetalGraphicsPipelineData
+{
+ id<MTLRenderPipelineState> ps = nil;
+ id<MTLDepthStencilState> ds = nil;
+ MTLPrimitiveType primitiveType;
+ MTLWinding winding;
+ MTLCullMode cullMode;
+ id<MTLLibrary> vsLib = nil;
+ id<MTLFunction> vsFunc = nil;
+ id<MTLLibrary> fsLib = nil;
+ id<MTLFunction> fsFunc = nil;
+};
+
+struct QMetalComputePipelineData
+{
+ id<MTLComputePipelineState> ps = nil;
+ id<MTLLibrary> csLib = nil;
+ id<MTLFunction> csFunc = nil;
+ MTLSize localSize;
+};
+
+struct QMetalSwapChainData
+{
+ CAMetalLayer *layer = nullptr;
+ id<CAMetalDrawable> curDrawable;
+ dispatch_semaphore_t sem[QMTL_FRAMES_IN_FLIGHT];
+ MTLRenderPassDescriptor *rp = nullptr;
+ id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
+ QRhiTexture::Format rhiColorFormat;
+ MTLPixelFormat colorFormat;
+};
+
+QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
+{
+ Q_UNUSED(params);
+
+ d = new QRhiMetalData(this);
+
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ if (d->dev) {
+ d->dev = (id<MTLDevice>) importDevice->dev;
+ importedCmdQueue = importDevice->cmdQueue != nullptr;
+ if (importedCmdQueue)
+ d->cmdQueue = (id<MTLCommandQueue>) importDevice->cmdQueue;
+ } else {
+ qWarning("No MTLDevice given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+QRhiMetal::~QRhiMetal()
+{
+ delete d;
+}
+
+static inline uint aligned(uint v, uint byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+bool QRhiMetal::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+
+ if (importedDevice)
+ [d->dev retain];
+ else
+ d->dev = MTLCreateSystemDefaultDevice();
+
+ qDebug("Metal device: %s", qPrintable(QString::fromNSString([d->dev name])));
+
+ if (importedCmdQueue)
+ [d->cmdQueue retain];
+ else
+ d->cmdQueue = [d->dev newCommandQueue];
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ d->captureMgr = [MTLCaptureManager sharedCaptureManager];
+ // Have a custom capture scope as well which then shows up in XCode as
+ // an option when capturing, and becomes especially useful when having
+ // multiple windows with multiple QRhis.
+ d->captureScope = [d->captureMgr newCaptureScopeWithCommandQueue: d->cmdQueue];
+ const QString label = QString::asprintf("Qt capture scope for QRhi %p", this);
+ d->captureScope.label = label.toNSString();
+ }
+
+#if defined(Q_OS_MACOS)
+ caps.maxTextureSize = 16384;
+#elif defined(Q_OS_TVOS)
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
+ caps.maxTextureSize = 16384;
+ else
+ caps.maxTextureSize = 8192;
+#elif defined(Q_OS_IOS)
+ // welcome to feature set hell
+ if ([d->dev supportsFeatureSet: MTLFeatureSet(16)] // MTLFeatureSet_iOS_GPUFamily5_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(11)] // MTLFeatureSet_iOS_GPUFamily4_v1
+ || [d->dev supportsFeatureSet: MTLFeatureSet(4)]) // MTLFeatureSet_iOS_GPUFamily3_v1
+ {
+ caps.maxTextureSize = 16384;
+ } else if ([d->dev supportsFeatureSet: MTLFeatureSet(3)] // MTLFeatureSet_iOS_GPUFamily2_v2
+ || [d->dev supportsFeatureSet: MTLFeatureSet(2)]) // MTLFeatureSet_iOS_GPUFamily1_v2
+ {
+ caps.maxTextureSize = 8192;
+ } else {
+ caps.maxTextureSize = 4096;
+ }
+#endif
+
+ nativeHandlesStruct.dev = d->dev;
+ nativeHandlesStruct.cmdQueue = d->cmdQueue;
+
+ return true;
+}
+
+void QRhiMetal::destroy()
+{
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ if (@available(macOS 10.13, iOS 11.0, *)) {
+ [d->captureScope release];
+ d->captureScope = nil;
+ }
+
+ [d->cmdQueue release];
+ if (!importedCmdQueue)
+ d->cmdQueue = nil;
+
+ [d->dev release];
+ if (!importedDevice)
+ d->dev = nil;
+}
+
+QVector<int> QRhiMetal::supportedSampleCounts() const
+{
+ return { 1, 2, 4, 8 };
+}
+
+int QRhiMetal::effectiveSampleCount(int sampleCount) const
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ const int s = qBound(1, sampleCount, 64);
+ if (!supportedSampleCounts().contains(s)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return 1;
+ }
+ return s;
+}
+
+QRhiSwapChain *QRhiMetal::createSwapChain()
+{
+ return new QMetalSwapChain(this);
+}
+
+QRhiBuffer *QRhiMetal::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QMetalBuffer(this, type, usage, size);
+}
+
+int QRhiMetal::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiMetal::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiMetal::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiMetal::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiMetal::clipSpaceCorrMatrix() const
+{
+ // depth range 0..1
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiMetal::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(flags);
+
+#ifdef Q_OS_MACOS
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8)
+ return false;
+ if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12)
+ return false;
+#else
+ if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7)
+ return false;
+#endif
+
+ return true;
+}
+
+bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return true;
+ case QRhi::Timestamps:
+ return false;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return true;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return false;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ case QRhi::Compute:
+ return true;
+ case QRhi::WideLines:
+ return false;
+ case QRhi::VertexShaderPointSize:
+ return true;
+ case QRhi::BaseVertex:
+ return true;
+ case QRhi::BaseInstance:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiMetal::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return caps.maxTextureSize;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return QMTL_FRAMES_IN_FLIGHT;
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiMetal::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+void QRhiMetal::makeThreadLocalNativeContextCurrent()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiMetal::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QMetalRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiMetal::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QMetalTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiMetal::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QMetalSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiMetal::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QMetalTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiMetal::createGraphicsPipeline()
+{
+ return new QMetalGraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiMetal::createComputePipeline()
+{
+ return new QMetalComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings()
+{
+ return new QMetalShaderResourceBindings(this);
+}
+
+void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
+ bool offsetOnlyChange)
+{
+ static const int KNOWN_STAGES = 3;
+ struct {
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> bufferOffsets;
+ QRhiBatchedBindings<id<MTLTexture> > textures;
+ QRhiBatchedBindings<id<MTLSamplerState> > samplers;
+ } res[KNOWN_STAGES];
+
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
+ uint offset = b->u.ubuf.offset;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ if (dynOfs.first == b->binding) {
+ offset = dynOfs.second;
+ break;
+ }
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].buffers.feed(b->binding, mtlbuf);
+ res[0].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].buffers.feed(b->binding, mtlbuf);
+ res[1].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].buffers.feed(b->binding, mtlbuf);
+ res[2].bufferOffsets.feed(b->binding, offset);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].textures.feed(b->binding, texD->d->tex);
+ res[0].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].textures.feed(b->binding, texD->d->tex);
+ res[1].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].textures.feed(b->binding, texD->d->tex);
+ res[2].samplers.feed(b->binding, samplerD->d->samplerState);
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ id<MTLTexture> t = texD->d->viewForLevel(b->u.simage.level);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage))
+ res[0].textures.feed(b->binding, t);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ res[1].textures.feed(b->binding, t);
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
+ res[2].textures.feed(b->binding, t);
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ id<MTLBuffer> mtlbuf = bufD->d->buf[0];
+ uint offset = b->u.sbuf.offset;
+ if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+ res[0].buffers.feed(b->binding, mtlbuf);
+ res[0].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+ res[1].buffers.feed(b->binding, mtlbuf);
+ res[1].bufferOffsets.feed(b->binding, offset);
+ }
+ if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+ res[2].buffers.feed(b->binding, mtlbuf);
+ res[2].bufferOffsets.feed(b->binding, offset);
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ for (int idx = 0; idx < KNOWN_STAGES; ++idx) {
+ res[idx].buffers.finish();
+ res[idx].bufferOffsets.finish();
+
+ for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(res[idx].buffers.batches[i]);
+ const auto &offsetBatch(res[idx].bufferOffsets.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(bufferBatch.startBinding, bufferBatch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ if (offsetOnlyChange)
+ continue;
+
+ res[idx].textures.finish();
+ res[idx].samplers.finish();
+
+ for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].textures.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) {
+ const auto &batch(res[idx].samplers.batches[i]);
+ switch (idx) {
+ case 0:
+ [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 1:
+ [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ case 2:
+ [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData()
+ withRange: NSMakeRange(batch.startBinding, batch.resources.count())];
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ }
+}
+
+void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ QMetalGraphicsPipeline *psD = QRHI_RES(QMetalGraphicsPipeline, ps);
+
+ if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ cbD->currentGraphicsPipeline = ps;
+ cbD->currentComputePipeline = nullptr;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ [cbD->d->currentRenderPassEncoder setRenderPipelineState: psD->d->ps];
+ [cbD->d->currentRenderPassEncoder setDepthStencilState: psD->d->ds];
+ [cbD->d->currentRenderPassEncoder setCullMode: psD->d->cullMode];
+ [cbD->d->currentRenderPassEncoder setFrontFacingWinding: psD->d->winding];
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass != QMetalCommandBuffer::NoPass);
+ QMetalGraphicsPipeline *gfxPsD = QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline);
+ QMetalComputePipeline *compPsD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
+
+ if (!srb) {
+ if (gfxPsD)
+ srb = gfxPsD->m_shaderResourceBindings;
+ else
+ srb = compPsD->m_shaderResourceBindings;
+ }
+
+ QMetalShaderResourceBindings *srbD = QRHI_RES(QMetalShaderResourceBindings, srb);
+ bool hasSlottedResourceInSrb = false;
+ bool hasDynamicOffsetInSrb = false;
+ bool resNeedsRebind = false;
+
+ // do buffer writes, figure out if we need to rebind, and mark as in-use
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+ executeBufferHostWritesForCurrentFrame(bufD);
+ if (bufD->d->slotted)
+ hasSlottedResourceInSrb = true;
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ resNeedsRebind = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ resNeedsRebind = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
+ resNeedsRebind = true;
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
+ executeBufferHostWritesForCurrentFrame(bufD);
+ if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
+ resNeedsRebind = true;
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ // make sure the resources for the correct slot get bound
+ const int resSlot = hasSlottedResourceInSrb ? currentFrameSlot : 0;
+ if (hasSlottedResourceInSrb && cbD->currentResSlot != resSlot)
+ resNeedsRebind = true;
+
+ const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+ const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
+
+ // dynamic uniform buffer offsets always trigger a rebind
+ if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) {
+ if (gfxPsD) {
+ cbD->currentGraphicsSrb = srb;
+ cbD->currentComputeSrb = nullptr;
+ } else {
+ cbD->currentGraphicsSrb = nullptr;
+ cbD->currentComputeSrb = srb;
+ }
+ cbD->currentSrbGeneration = srbD->generation;
+ cbD->currentResSlot = resSlot;
+
+ const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt;
+ enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange);
+ }
+}
+
+void QRhiMetal::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ QRhiBatchedBindings<id<MTLBuffer> > buffers;
+ QRhiBatchedBindings<NSUInteger> offsets;
+ for (int i = 0; i < bindingCount; ++i) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, bindings[i].first);
+ executeBufferHostWritesForCurrentFrame(bufD);
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ id<MTLBuffer> mtlbuf = bufD->d->buf[bufD->d->slotted ? currentFrameSlot : 0];
+ buffers.feed(startBinding + i, mtlbuf);
+ offsets.feed(startBinding + i, bindings[i].second);
+ }
+ buffers.finish();
+ offsets.finish();
+
+ // same binding space for vertex and constant buffers - work it around
+ QRhiShaderResourceBindings *srb = cbD->currentGraphicsSrb;
+ // There's nothing guaranteeing setShaderResources() was called before
+ // setVertexInput()... but whatever srb will get bound will have to be
+ // layout-compatible anyways so maxBinding is the same.
+ if (!srb)
+ srb = cbD->currentGraphicsPipeline->shaderResourceBindings();
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, srb)->maxBinding + 1;
+
+ if (firstVertexBinding != cbD->d->currentFirstVertexBinding
+ || buffers != cbD->d->currentVertexInputsBuffers
+ || offsets != cbD->d->currentVertexInputOffsets)
+ {
+ cbD->d->currentFirstVertexBinding = firstVertexBinding;
+ cbD->d->currentVertexInputsBuffers = buffers;
+ cbD->d->currentVertexInputOffsets = offsets;
+
+ for (int i = 0, ie = buffers.batches.count(); i != ie; ++i) {
+ const auto &bufferBatch(buffers.batches[i]);
+ const auto &offsetBatch(offsets.batches[i]);
+ [cbD->d->currentRenderPassEncoder setVertexBuffers:
+ bufferBatch.resources.constData()
+ offsets: offsetBatch.resources.constData()
+ withRange: NSMakeRange(firstVertexBinding + bufferBatch.startBinding, bufferBatch.resources.count())];
+ }
+ }
+
+ if (indexBuf) {
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, indexBuf);
+ executeBufferHostWritesForCurrentFrame(ibufD);
+ ibufD->lastActiveFrameSlot = currentFrameSlot;
+ cbD->currentIndexBuffer = indexBuf;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = indexFormat;
+ } else {
+ cbD->currentIndexBuffer = nullptr;
+ }
+}
+
+void QRhiMetal::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLViewportRect but bottom-left in QRhiViewport
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ MTLViewport vp;
+ vp.originX = x;
+ vp.originY = y;
+ vp.width = w;
+ vp.height = h;
+ vp.znear = viewport.minDepth();
+ vp.zfar = viewport.maxDepth();
+
+ [cbD->d->currentRenderPassEncoder setViewport: vp];
+
+ if (!QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+ [cbD->d->currentRenderPassEncoder setScissorRect: s];
+ }
+}
+
+void QRhiMetal::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+ Q_ASSERT(QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in MTLScissorRect but bottom-left in QRhiScissor
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ MTLScissorRect s;
+ s.x = x;
+ s.y = y;
+ s.width = w;
+ s.height = h;
+
+ [cbD->d->currentRenderPassEncoder setScissorRect: s];
+}
+
+void QRhiMetal::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder setBlendColorRed: c.redF() green: c.greenF() blue: c.blueF() alpha: c.alphaF()];
+}
+
+void QRhiMetal::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder setStencilReferenceValue: refValue];
+}
+
+void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder drawPrimitives:
+ QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
+}
+
+void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ if (!cbD->currentIndexBuffer)
+ return;
+
+ const quint32 indexOffset = cbD->currentIndexOffset + firstIndex * (cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? 2 : 4);
+ Q_ASSERT(indexOffset == aligned(indexOffset, 4));
+
+ QMetalBuffer *ibufD = QRHI_RES(QMetalBuffer, cbD->currentIndexBuffer);
+ id<MTLBuffer> mtlbuf = ibufD->d->buf[ibufD->d->slotted ? currentFrameSlot : 0];
+
+ [cbD->d->currentRenderPassEncoder drawIndexedPrimitives: QRHI_RES(QMetalGraphicsPipeline, cbD->currentGraphicsPipeline)->d->primitiveType
+ indexCount: indexCount
+ indexType: cbD->currentIndexFormat == QRhiCommandBuffer::IndexUInt16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32
+ indexBuffer: mtlbuf
+ indexBufferOffset: indexOffset
+ instanceCount: instanceCount
+ baseVertex: vertexOffset
+ baseInstance: firstInstance];
+}
+
+void QRhiMetal::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers)
+ return;
+
+ NSString *str = [NSString stringWithUTF8String: name.constData()];
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
+ [cbD->d->currentRenderPassEncoder pushDebugGroup: str];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb pushDebugGroup: str];
+ }
+}
+
+void QRhiMetal::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers)
+ return;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass) {
+ [cbD->d->currentRenderPassEncoder popDebugGroup];
+ } else {
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [cbD->d->cb popDebugGroup];
+ }
+}
+
+void QRhiMetal::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers)
+ return;
+
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ if (cbD->recordingPass != QMetalCommandBuffer::NoPass)
+ [cbD->d->currentRenderPassEncoder insertDebugSignpost: [NSString stringWithUTF8String: msg.constData()]];
+}
+
+const QRhiNativeHandles *QRhiMetal::nativeHandles(QRhiCommandBuffer *cb)
+{
+ return QRHI_RES(QMetalCommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiMetal::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiMetal::endExternal(QRhiCommandBuffer *cb)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ cbD->resetPerPassCachedState();
+}
+
+QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+
+ // This is a bit messed up since for this swapchain we want to wait for the
+ // commands+present to complete, while for others just for the commands
+ // (for this same frame slot) but not sure how to do that in a sane way so
+ // wait for full cb completion for now.
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ dispatch_semaphore_t sem = sc->d->sem[swapChainD->currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ if (sc != swapChainD)
+ dispatch_semaphore_signal(sem);
+ }
+
+ currentSwapChain = swapChainD;
+ currentFrameSlot = swapChainD->currentFrameSlot;
+ if (swapChainD->ds)
+ swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope beginScope];
+
+ // Do not let the command buffer mess with the refcount of objects. We do
+ // have a proper render loop and will manage lifetimes similarly to other
+ // backends (Vulkan).
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ if (swapChainD->samples > 1) {
+ colorAtt.tex = swapChainD->d->msaaTex[currentFrameSlot];
+ colorAtt.needsDrawableForResolveTex = true;
+ } else {
+ colorAtt.needsDrawableForTex = true;
+ }
+
+ swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
+ swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
+ swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
+ swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ executeDeferredReleases();
+ swapChainD->cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
+ if (needsPresent) {
+ [swapChainD->cbWrapper.d->cb presentDrawable: swapChainD->d->curDrawable];
+ swapChainD->d->curDrawable = nil;
+ }
+
+ __block int thisFrameSlot = currentFrameSlot;
+ [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer>) {
+ dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
+ }];
+
+ [swapChainD->cbWrapper.d->cb commit];
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (@available(macOS 10.13, iOS 11.0, *))
+ [d->captureScope endScope];
+
+ if (needsPresent)
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ currentFrameSlot = (currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
+ if (swapchains.count() > 1) {
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ // wait+signal is the general pattern to ensure the commands for a
+ // given frame slot have completed (if sem is 1, we go 0 then 1; if
+ // sem is 0 we go -1, block, completion increments to 0, then us to 1)
+ dispatch_semaphore_t sem = sc->d->sem[currentFrameSlot];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ d->ofr.active = true;
+ *cb = &d->ofr.cbWrapper;
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+
+ executeDeferredReleases();
+ d->ofr.cbWrapper.resetState();
+ finishActiveReadbacks();
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::endOffscreenFrame()
+{
+ Q_ASSERT(d->ofr.active);
+ d->ofr.active = false;
+
+ [d->ofr.cbWrapper.d->cb commit];
+
+ // offscreen frames wait for completion, unlike swapchain ones
+ [d->ofr.cbWrapper.d->cb waitUntilCompleted];
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiMetal::finish()
+{
+ id<MTLCommandBuffer> cb = nil;
+ QMetalSwapChain *swapChainD = nullptr;
+ if (inFrame) {
+ if (d->ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ Q_ASSERT(d->ofr.cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
+ cb = d->ofr.cbWrapper.d->cb;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = currentSwapChain;
+ Q_ASSERT(swapChainD->cbWrapper.recordingPass == QMetalCommandBuffer::NoPass);
+ cb = swapChainD->cbWrapper.d->cb;
+ }
+ }
+
+ for (QMetalSwapChain *sc : qAsConst(swapchains)) {
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (currentSwapChain && sc == currentSwapChain && i == currentFrameSlot) {
+ // no wait as this is the thing we're going to be commit below and
+ // beginFrame decremented sem already and going to be signaled by endFrame
+ continue;
+ }
+ dispatch_semaphore_t sem = sc->d->sem[i];
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(sem);
+ }
+ }
+
+ if (cb) {
+ [cb commit];
+ [cb waitUntilCompleted];
+ }
+
+ if (inFrame) {
+ if (d->ofr.active)
+ d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ else
+ swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ }
+
+ executeDeferredReleases(true);
+
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+MTLRenderPassDescriptor *QRhiMetalData::createDefaultRenderPass(bool hasDepthStencil,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ int colorAttCount)
+{
+ MTLRenderPassDescriptor *rp = [MTLRenderPassDescriptor renderPassDescriptor];
+ MTLClearColor c = MTLClearColorMake(colorClearValue.redF(), colorClearValue.greenF(), colorClearValue.blueF(),
+ colorClearValue.alphaF());
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ rp.colorAttachments[i].loadAction = MTLLoadActionClear;
+ rp.colorAttachments[i].storeAction = MTLStoreActionStore;
+ rp.colorAttachments[i].clearColor = c;
+ }
+
+ if (hasDepthStencil) {
+ rp.depthAttachment.loadAction = MTLLoadActionClear;
+ rp.depthAttachment.storeAction = MTLStoreActionDontCare;
+ rp.stencilAttachment.loadAction = MTLLoadActionClear;
+ rp.stencilAttachment.storeAction = MTLStoreActionDontCare;
+ rp.depthAttachment.clearDepth = depthStencilClearValue.depthClearValue();
+ rp.stencilAttachment.clearStencil = depthStencilClearValue.stencilClearValue();
+ }
+
+ return rp;
+}
+
+qsizetype QRhiMetal::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
+{
+ qsizetype size = 0;
+ const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
+ subresDesc.data().size() : subresDesc.image().sizeInBytes();
+ if (imageSizeBytes > 0)
+ size += aligned(imageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ return size;
+}
+
+void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
+ qsizetype *curOfs)
+{
+ const QPoint dp = subresDesc.destinationTopLeft();
+ const QByteArray rawData = subresDesc.data();
+ QImage img = subresDesc.image();
+ id<MTLBlitCommandEncoder> blitEnc = (id<MTLBlitCommandEncoder>) blitEncPtr;
+
+ if (!img.isNull()) {
+ const qsizetype fullImageSizeBytes = img.sizeInBytes();
+ int w = img.width();
+ int h = img.height();
+ int bpl = img.bytesPerLine();
+ int srcOffset = 0;
+
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const int sx = subresDesc.sourceTopLeft().x();
+ const int sy = subresDesc.sourceTopLeft().y();
+ if (!subresDesc.sourceSize().isEmpty()) {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+ if (img.depth() == 32) {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ srcOffset = sy * bpl + sx * 4;
+ // bpl remains set to the original image's row stride
+ } else {
+ img = img.copy(sx, sy, w, h);
+ bpl = img.bytesPerLine();
+ Q_ASSERT(img.sizeInBytes() <= fullImageSizeBytes);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), img.sizeInBytes());
+ }
+ } else {
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), fullImageSizeBytes);
+ }
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs + srcOffset
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(fullImageSizeBytes, QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr, &blockDim);
+
+ const int dx = aligned(dp.x(), blockDim.width());
+ const int dy = aligned(dp.y(), blockDim.height());
+ if (dx + w != subresw)
+ w = aligned(w, blockDim.width());
+ if (dy + h != subresh)
+ h = aligned(h, blockDim.height());
+
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dx, dy, 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else if (!rawData.isEmpty()) {
+ const QSize subresSize = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = subresSize.width();
+ const int subresh = subresSize.height();
+ int w, h;
+ if (subresDesc.sourceSize().isEmpty()) {
+ w = subresw;
+ h = subresh;
+ } else {
+ w = subresDesc.sourceSize().width();
+ h = subresDesc.sourceSize().height();
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(texD->m_format, QSize(w, h), &bpl, nullptr);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, rawData.constData(), rawData.size());
+
+ [blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
+ sourceOffset: *curOfs
+ sourceBytesPerRow: bpl
+ sourceBytesPerImage: 0
+ sourceSize: MTLSizeMake(w, h, 1)
+ toTexture: texD->d->tex
+ destinationSlice: layer
+ destinationLevel: level
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)
+ options: MTLBlitOptionNone];
+
+ *curOfs += aligned(rawData.size(), QRhiMetalData::TEXBUF_ALIGN);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+}
+
+void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ bufD->d->pendingUpdates[i].append(u);
+ }
+
+ // Due to the Metal API the handling of static and dynamic buffers is
+ // basically the same. So go through the same pendingUpdates machinery.
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+ for (int i = 0, ie = bufD->d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1; i != ie; ++i)
+ bufD->d->pendingUpdates[i].append({ u.buf, u.offset, u.data.size(), u.data.constData() });
+ }
+
+ id<MTLBlitCommandEncoder> blitEnc = nil;
+ auto ensureBlit = [&blitEnc, cbD, this] {
+ if (!blitEnc) {
+ blitEnc = [cbD->d->cb blitCommandEncoder];
+ if (debugMarkers)
+ [blitEnc pushDebugGroup: @"Texture upload/copy"];
+ }
+ };
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.upload.tex);
+ qsizetype stagingSize = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ stagingSize += subresUploadByteSize(subresDesc);
+ }
+ }
+
+ ensureBlit();
+ Q_ASSERT(!utexD->d->stagingBuf[currentFrameSlot]);
+ utexD->d->stagingBuf[currentFrameSlot] = [d->dev newBufferWithLength: stagingSize
+ options: MTLResourceStorageModeShared];
+ QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
+
+ void *mp = [utexD->d->stagingBuf[currentFrameSlot] contents];
+ qsizetype curOfs = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ enqueueSubresUpload(utexD, mp, blitEnc, layer, level, subresDesc, &curOfs);
+ }
+ }
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.buffer = utexD->d->stagingBuf[currentFrameSlot];
+ utexD->d->stagingBuf[currentFrameSlot] = nil;
+ d->releaseQueue.append(e);
+ QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ QMetalTexture *srcD = QRHI_RES(QMetalTexture, u.copy.src);
+ QMetalTexture *dstD = QRHI_RES(QMetalTexture, u.copy.dst);
+ const QPoint dp = u.copy.desc.destinationTopLeft();
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ const QPoint sp = u.copy.desc.sourceTopLeft();
+
+ ensureBlit();
+ [blitEnc copyFromTexture: srcD->d->tex
+ sourceSlice: u.copy.desc.sourceLayer()
+ sourceLevel: u.copy.desc.sourceLevel()
+ sourceOrigin: MTLOriginMake(sp.x(), sp.y(), 0)
+ sourceSize: MTLSizeMake(size.width(), size.height(), 1)
+ toTexture: dstD->d->tex
+ destinationSlice: u.copy.desc.destinationLayer()
+ destinationLevel: u.copy.desc.destinationLevel()
+ destinationOrigin: MTLOriginMake(dp.x(), dp.y(), 0)];
+
+ srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QRhiMetalData::ActiveReadback aRb;
+ aRb.activeFrameSlot = currentFrameSlot;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, u.read.rb.texture());
+ QMetalSwapChain *swapChainD = nullptr;
+ id<MTLTexture> src;
+ QSize srcSize;
+ if (texD) {
+ if (texD->samples > 1) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
+ : texD->m_pixelSize;
+ aRb.format = texD->m_format;
+ src = texD->d->tex;
+ srcSize = texD->m_pixelSize;
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ aRb.pixelSize = swapChainD->pixelSize;
+ aRb.format = swapChainD->d->rhiColorFormat;
+ // Multisample swapchains need nothing special since resolving
+ // happens when ending a renderpass.
+ const QMetalRenderTargetData::ColorAtt &colorAtt(swapChainD->rtWrapper.d->fb.colorAtt[0]);
+ src = colorAtt.resolveTex ? colorAtt.resolveTex : colorAtt.tex;
+ srcSize = swapChainD->rtWrapper.d->pixelSize;
+ }
+
+ quint32 bpl = 0;
+ textureFormatInfo(aRb.format, aRb.pixelSize, &bpl, &aRb.bufSize);
+ aRb.buf = [d->dev newBufferWithLength: aRb.bufSize options: MTLResourceStorageModeShared];
+
+ QRHI_PROF_F(newReadbackBuffer(quint64(quintptr(aRb.buf)),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ aRb.bufSize));
+
+ ensureBlit();
+ [blitEnc copyFromTexture: src
+ sourceSlice: u.read.rb.layer()
+ sourceLevel: u.read.rb.level()
+ sourceOrigin: MTLOriginMake(0, 0, 0)
+ sourceSize: MTLSizeMake(srcSize.width(), srcSize.height(), 1)
+ toBuffer: aRb.buf
+ destinationOffset: 0
+ destinationBytesPerRow: bpl
+ destinationBytesPerImage: 0
+ options: MTLBlitOptionNone];
+
+ d->activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QMetalTexture *utexD = QRHI_RES(QMetalTexture, u.mipgen.tex);
+ ensureBlit();
+ [blitEnc generateMipmapsForTexture: utexD->d->tex];
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+
+ if (blitEnc) {
+ if (debugMarkers)
+ [blitEnc popDebugGroup];
+ [blitEnc endEncoding];
+ }
+
+ ud->free();
+}
+
+// this handles all types of buffers, not just Dynamic
+void QRhiMetal::executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD)
+{
+ const int idx = bufD->d->slotted ? currentFrameSlot : 0;
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->d->pendingUpdates[idx]);
+ if (updates.isEmpty())
+ return;
+
+ void *p = [bufD->d->buf[idx] contents];
+ int changeBegin = -1;
+ int changeEnd = -1;
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
+ Q_ASSERT(bufD == QRHI_RES(QMetalBuffer, u.buf));
+ memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
+ if (changeBegin == -1 || u.offset < changeBegin)
+ changeBegin = u.offset;
+ if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
+ changeEnd = u.offset + u.data.size();
+ }
+ if (changeBegin >= 0 && bufD->d->managed)
+ [bufD->d->buf[idx] didModifyRange: NSMakeRange(changeBegin, changeEnd - changeBegin)];
+
+ updates.clear();
+}
+
+void QRhiMetal::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_ASSERT(QRHI_RES(QMetalCommandBuffer, cb)->recordingPass == QMetalCommandBuffer::NoPass);
+
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ QMetalRenderTargetData *rtD = nullptr;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = QRHI_RES(QMetalReferenceRenderTarget, rt)->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtD->colorAttCount) {
+ QMetalRenderTargetData::ColorAtt &color0(rtD->fb.colorAtt[0]);
+ if (color0.needsDrawableForTex || color0.needsDrawableForResolveTex) {
+ Q_ASSERT(currentSwapChain);
+ QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, currentSwapChain);
+ if (!swapChainD->d->curDrawable)
+ swapChainD->d->curDrawable = [swapChainD->d->layer nextDrawable];
+ if (!swapChainD->d->curDrawable) {
+ qWarning("No drawable");
+ return;
+ }
+ id<MTLTexture> scTex = swapChainD->d->curDrawable.texture;
+ if (color0.needsDrawableForTex) {
+ color0.tex = scTex;
+ color0.needsDrawableForTex = false;
+ } else {
+ color0.resolveTex = scTex;
+ color0.needsDrawableForResolveTex = false;
+ }
+ }
+ }
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QMetalTextureRenderTarget *rtTex = QRHI_RES(QMetalTextureRenderTarget, rt);
+ rtD = rtTex->d;
+ cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
+ if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
+ for (int i = 0; i < rtD->colorAttCount; ++i)
+ cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
+ }
+ if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
+ cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
+ cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
+ }
+ const QVector<QRhiColorAttachment> colorAttachments = rtTex->m_desc.colorAttachments();
+ for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
+ if (colorAttachment.texture())
+ QRHI_RES(QMetalTexture, colorAttachment.texture())->lastActiveFrameSlot = currentFrameSlot;
+ else if (colorAttachment.renderBuffer())
+ QRHI_RES(QMetalRenderBuffer, colorAttachment.renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (colorAttachment.resolveTexture())
+ QRHI_RES(QMetalTexture, colorAttachment.resolveTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ if (rtTex->m_desc.depthStencilBuffer())
+ QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtTex->m_desc.depthTexture())
+ QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ for (int i = 0; i < rtD->colorAttCount; ++i) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].texture = rtD->fb.colorAtt[i].tex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].slice = rtD->fb.colorAtt[i].layer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].level = rtD->fb.colorAtt[i].level;
+ if (rtD->fb.colorAtt[i].resolveTex) {
+ cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = rtD->fb.colorAtt[i].resolveLayer;
+ cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = rtD->fb.colorAtt[i].resolveLevel;
+ }
+ }
+
+ if (rtD->dsAttCount) {
+ Q_ASSERT(rtD->fb.dsTex);
+ cbD->d->currentPassRpDesc.depthAttachment.texture = rtD->fb.dsTex;
+ cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
+ if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
+ cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
+ }
+
+ cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
+
+ cbD->resetPerPassState();
+
+ cbD->recordingPass = QMetalCommandBuffer::RenderPass;
+ cbD->currentTarget = rt;
+}
+
+void QRhiMetal::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::RenderPass);
+
+ [cbD->d->currentRenderPassEncoder endEncoding];
+
+ cbD->recordingPass = QMetalCommandBuffer::NoPass;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+
+ cbD->d->currentComputePassEncoder = [cbD->d->cb computeCommandEncoder];
+ cbD->resetPerPassState();
+ cbD->recordingPass = QMetalCommandBuffer::ComputePass;
+}
+
+void QRhiMetal::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+
+ [cbD->d->currentComputePassEncoder endEncoding];
+ cbD->recordingPass = QMetalCommandBuffer::NoPass;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiMetal::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+ QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, ps);
+
+ if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ cbD->currentGraphicsPipeline = nullptr;
+ cbD->currentComputePipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+
+ [cbD->d->currentComputePassEncoder setComputePipelineState: psD->d->ps];
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiMetal::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QMetalCommandBuffer::ComputePass);
+ QMetalComputePipeline *psD = QRHI_RES(QMetalComputePipeline, cbD->currentComputePipeline);
+
+ [cbD->d->currentComputePassEncoder dispatchThreadgroups: MTLSizeMake(x, y, z)
+ threadsPerThreadgroup: psD->d->localSize];
+}
+
+static void qrhimtl_releaseBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.buffer.buffers[i] release];
+}
+
+static void qrhimtl_releaseRenderBuffer(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.renderbuffer.texture release];
+}
+
+static void qrhimtl_releaseTexture(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.texture.texture release];
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ [e.texture.stagingBuffers[i] release];
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ [e.texture.views[i] release];
+}
+
+static void qrhimtl_releaseSampler(const QRhiMetalData::DeferredReleaseEntry &e)
+{
+ [e.sampler.samplerState release];
+}
+
+void QRhiMetal::executeDeferredReleases(bool forced)
+{
+ for (int i = d->releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::DeferredReleaseEntry &e(d->releaseQueue[i]);
+ if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
+ switch (e.type) {
+ case QRhiMetalData::DeferredReleaseEntry::Buffer:
+ qrhimtl_releaseBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::RenderBuffer:
+ qrhimtl_releaseRenderBuffer(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Texture:
+ qrhimtl_releaseTexture(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::Sampler:
+ qrhimtl_releaseSampler(e);
+ break;
+ case QRhiMetalData::DeferredReleaseEntry::StagingBuffer:
+ [e.stagingBuffer.buffer release];
+ break;
+ default:
+ break;
+ }
+ d->releaseQueue.removeAt(i);
+ }
+ }
+}
+
+void QRhiMetal::finishActiveReadbacks(bool forced)
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = d->activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiMetalData::ActiveReadback &aRb(d->activeReadbacks[i]);
+ if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+ void *p = [aRb.buf contents];
+ memcpy(aRb.result->data.data(), p, aRb.bufSize);
+ [aRb.buf release];
+
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(quintptr(aRb.buf))));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ d->activeReadbacks.removeAt(i);
+ }
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+QMetalBuffer::QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size),
+ d(new QMetalBufferData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->buf[i] = nil;
+}
+
+QMetalBuffer::~QMetalBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalBuffer::release()
+{
+ if (!d->buf[0])
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Buffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.buffer.buffers[i] = d->buf[i];
+ d->buf[i] = nil;
+ d->pendingUpdates[i].clear();
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalBuffer::build()
+{
+ if (d->buf[0])
+ release();
+
+ if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+ qWarning("StorageBuffer cannot be combined with Dynamic");
+ return false;
+ }
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+ const int roundedSize = m_usage.testFlag(QRhiBuffer::UniformBuffer) ? aligned(nonZeroSize, 256) : nonZeroSize;
+
+ d->managed = false;
+ MTLResourceOptions opts = MTLResourceStorageModeShared;
+#ifdef Q_OS_MACOS
+ if (m_type != Dynamic) {
+ opts = MTLResourceStorageModeManaged;
+ d->managed = true;
+ }
+#endif
+
+ // Immutable and Static only has buf[0] and pendingUpdates[0] in use.
+ // Dynamic uses all.
+ d->slotted = m_type == Dynamic;
+
+ QRHI_RES_RHI(QRhiMetal);
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (i == 0 || d->slotted) {
+ d->buf[i] = [rhiD->d->dev newBufferWithLength: roundedSize options: opts];
+ d->pendingUpdates[i].reserve(16);
+ if (!m_objectName.isEmpty()) {
+ if (!d->slotted) {
+ d->buf[i].label = [NSString stringWithUTF8String: m_objectName.constData()];
+ } else {
+ const QByteArray name = m_objectName + '/' + QByteArray::number(i);
+ d->buf[i].label = [NSString stringWithUTF8String: name.constData()];
+ }
+ }
+ }
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, roundedSize, d->slotted ? QMTL_FRAMES_IN_FLIGHT : 1, 0));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalRenderBuffer::QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags),
+ d(new QMetalRenderBufferData)
+{
+}
+
+QMetalRenderBuffer::~QMetalRenderBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalRenderBuffer::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::RenderBuffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderbuffer.texture = d->tex;
+ d->tex = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalRenderBuffer::build()
+{
+ if (d->tex)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.width = m_pixelSize.width();
+ desc.height = m_pixelSize.height();
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+
+ bool transientBacking = false;
+ switch (m_type) {
+ case DepthStencil:
+#ifdef Q_OS_MACOS
+ desc.storageMode = MTLStorageModePrivate;
+#else
+ desc.storageMode = MTLResourceStorageModeMemoryless;
+ transientBacking = true;
+#endif
+ d->format = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+ desc.pixelFormat = d->format;
+ break;
+ case Color:
+ desc.storageMode = MTLStorageModePrivate;
+ d->format = MTLPixelFormatRGBA8Unorm;
+ desc.pixelFormat = d->format;
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, transientBacking, false, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QMetalRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QMetalTexture::QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags),
+ d(new QMetalTextureData(this))
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i)
+ d->stagingBuf[i] = nil;
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ d->perLevelViews[i] = nil;
+}
+
+QMetalTexture::~QMetalTexture()
+{
+ release();
+ delete d;
+}
+
+void QMetalTexture::release()
+{
+ if (!d->tex)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Texture;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.texture.texture = d->owns ? d->tex : nil;
+ d->tex = nil;
+ nativeHandlesStruct.texture = nullptr;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ e.texture.stagingBuffers[i] = d->stagingBuf[i];
+ d->stagingBuf[i] = nil;
+ }
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
+ e.texture.views[i] = d->perLevelViews[i];
+ d->perLevelViews[i] = nil;
+ }
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLPixelFormat toMetalTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? MTLPixelFormatRGBA8Unorm_sRGB : MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BGRA8:
+ return srgb ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ case QRhiTexture::R8:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatR8Unorm;
+#else
+ return srgb ? MTLPixelFormatR8Unorm_sRGB : MTLPixelFormatR8Unorm;
+#endif
+ case QRhiTexture::R16:
+ return MTLPixelFormatR16Unorm;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return MTLPixelFormatR8Unorm;
+
+ case QRhiTexture::RGBA16F:
+ return MTLPixelFormatRGBA16Float;
+ case QRhiTexture::RGBA32F:
+ return MTLPixelFormatRGBA32Float;
+
+ case QRhiTexture::D16:
+#ifdef Q_OS_MACOS
+ return MTLPixelFormatDepth16Unorm;
+#else
+ return MTLPixelFormatDepth32Float;
+#endif
+ case QRhiTexture::D32F:
+ return MTLPixelFormatDepth32Float;
+
+#ifdef Q_OS_MACOS
+ case QRhiTexture::BC1:
+ return srgb ? MTLPixelFormatBC1_RGBA_sRGB : MTLPixelFormatBC1_RGBA;
+ case QRhiTexture::BC2:
+ return srgb ? MTLPixelFormatBC2_RGBA_sRGB : MTLPixelFormatBC2_RGBA;
+ case QRhiTexture::BC3:
+ return srgb ? MTLPixelFormatBC3_RGBA_sRGB : MTLPixelFormatBC3_RGBA;
+ case QRhiTexture::BC4:
+ return MTLPixelFormatBC4_RUnorm;
+ case QRhiTexture::BC5:
+ qWarning("QRhiMetal does not support BC5");
+ return MTLPixelFormatRGBA8Unorm;
+ case QRhiTexture::BC6H:
+ return MTLPixelFormatBC6H_RGBUfloat;
+ case QRhiTexture::BC7:
+ return srgb ? MTLPixelFormatBC7_RGBAUnorm_sRGB : MTLPixelFormatBC7_RGBAUnorm;
+#else
+ case QRhiTexture::BC1:
+ case QRhiTexture::BC2:
+ case QRhiTexture::BC3:
+ case QRhiTexture::BC4:
+ case QRhiTexture::BC5:
+ case QRhiTexture::BC6H:
+ case QRhiTexture::BC7:
+ qWarning("QRhiMetal: BCx compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+#ifndef Q_OS_MACOS
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? MTLPixelFormatETC2_RGB8_sRGB : MTLPixelFormatETC2_RGB8;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? MTLPixelFormatETC2_RGB8A1_sRGB : MTLPixelFormatETC2_RGB8A1;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? MTLPixelFormatEAC_RGBA8_sRGB : MTLPixelFormatEAC_RGBA8;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? MTLPixelFormatASTC_4x4_sRGB : MTLPixelFormatASTC_4x4_LDR;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? MTLPixelFormatASTC_5x4_sRGB : MTLPixelFormatASTC_5x4_LDR;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? MTLPixelFormatASTC_5x5_sRGB : MTLPixelFormatASTC_5x5_LDR;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? MTLPixelFormatASTC_6x5_sRGB : MTLPixelFormatASTC_6x5_LDR;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? MTLPixelFormatASTC_6x6_sRGB : MTLPixelFormatASTC_6x6_LDR;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? MTLPixelFormatASTC_8x5_sRGB : MTLPixelFormatASTC_8x5_LDR;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? MTLPixelFormatASTC_8x6_sRGB : MTLPixelFormatASTC_8x6_LDR;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? MTLPixelFormatASTC_8x8_sRGB : MTLPixelFormatASTC_8x8_LDR;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? MTLPixelFormatASTC_10x5_sRGB : MTLPixelFormatASTC_10x5_LDR;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? MTLPixelFormatASTC_10x6_sRGB : MTLPixelFormatASTC_10x6_LDR;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? MTLPixelFormatASTC_10x8_sRGB : MTLPixelFormatASTC_10x8_LDR;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? MTLPixelFormatASTC_10x10_sRGB : MTLPixelFormatASTC_10x10_LDR;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? MTLPixelFormatASTC_12x10_sRGB : MTLPixelFormatASTC_12x10_LDR;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? MTLPixelFormatASTC_12x12_sRGB : MTLPixelFormatASTC_12x12_LDR;
+#else
+ case QRhiTexture::ETC2_RGB8:
+ case QRhiTexture::ETC2_RGB8A1:
+ case QRhiTexture::ETC2_RGBA8:
+ qWarning("QRhiMetal: ETC2 compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+
+ case QRhiTexture::ASTC_4x4:
+ case QRhiTexture::ASTC_5x4:
+ case QRhiTexture::ASTC_5x5:
+ case QRhiTexture::ASTC_6x5:
+ case QRhiTexture::ASTC_6x6:
+ case QRhiTexture::ASTC_8x5:
+ case QRhiTexture::ASTC_8x6:
+ case QRhiTexture::ASTC_8x8:
+ case QRhiTexture::ASTC_10x5:
+ case QRhiTexture::ASTC_10x6:
+ case QRhiTexture::ASTC_10x8:
+ case QRhiTexture::ASTC_10x10:
+ case QRhiTexture::ASTC_12x10:
+ case QRhiTexture::ASTC_12x12:
+ qWarning("QRhiMetal: ASTC compression not supported on this platform");
+ return MTLPixelFormatRGBA8Unorm;
+#endif
+
+ default:
+ Q_UNREACHABLE();
+ return MTLPixelFormatRGBA8Unorm;
+ }
+}
+
+bool QMetalTexture::prepareBuild(QSize *adjustedSize)
+{
+ if (d->tex)
+ release();
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->format = toMetalTextureFormat(m_format, m_flags);
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ if (samples > 1) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QMetalTexture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+
+ const bool isCube = m_flags.testFlag(CubeMap);
+ if (isCube)
+ desc.textureType = MTLTextureTypeCube;
+ else
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;
+ desc.pixelFormat = d->format;
+ desc.width = size.width();
+ desc.height = size.height();
+ desc.mipmapLevelCount = mipLevelCount;
+ if (samples > 1)
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageShaderRead;
+ if (m_flags.testFlag(RenderTarget))
+ desc.usage |= MTLTextureUsageRenderTarget;
+ if (m_flags.testFlag(UsedWithLoadStore))
+ desc.usage |= MTLTextureUsageShaderWrite;
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->tex = [rhiD->d->dev newTextureWithDescriptor: desc];
+ [desc release];
+
+ if (!m_objectName.isEmpty())
+ d->tex.label = [NSString stringWithUTF8String: m_objectName.constData()];
+
+ d->owns = true;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QMetalTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiMetalTextureNativeHandles *h = static_cast<const QRhiMetalTextureNativeHandles *>(src);
+ if (!h || !h->texture)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ d->tex = (id<MTLTexture>) h->texture;
+
+ d->owns = false;
+ nativeHandlesStruct.texture = d->tex;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QMetalTexture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+id<MTLTexture> QMetalTextureData::viewForLevel(int level)
+{
+ Q_ASSERT(level >= 0 && level < int(q->mipLevelCount));
+ if (perLevelViews[level])
+ return perLevelViews[level];
+
+ const MTLTextureType type = [tex textureType];
+ const bool isCube = q->m_flags.testFlag(QRhiTexture::CubeMap);
+ id<MTLTexture> view = [tex newTextureViewWithPixelFormat: format textureType: type
+ levels: NSMakeRange(level, 1) slices: NSMakeRange(0, isCube ? 6 : 1)];
+
+ perLevelViews[level] = view;
+ return view;
+}
+
+QMetalSampler::QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v),
+ d(new QMetalSamplerData)
+{
+}
+
+QMetalSampler::~QMetalSampler()
+{
+ release();
+ delete d;
+}
+
+void QMetalSampler::release()
+{
+ if (!d->samplerState)
+ return;
+
+ QRhiMetalData::DeferredReleaseEntry e;
+ e.type = QRhiMetalData::DeferredReleaseEntry::Sampler;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.sampler.samplerState = d->samplerState;
+ d->samplerState = nil;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->d->releaseQueue.append(e);
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLSamplerMinMagFilter toMetalFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return MTLSamplerMinMagFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMinMagFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMinMagFilterNearest;
+ }
+}
+
+static inline MTLSamplerMipFilter toMetalMipmapMode(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::None:
+ return MTLSamplerMipFilterNotMipmapped;
+ case QRhiSampler::Nearest:
+ return MTLSamplerMipFilterNearest;
+ case QRhiSampler::Linear:
+ return MTLSamplerMipFilterLinear;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerMipFilterNotMipmapped;
+ }
+}
+
+static inline MTLSamplerAddressMode toMetalAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return MTLSamplerAddressModeRepeat;
+ case QRhiSampler::ClampToEdge:
+ return MTLSamplerAddressModeClampToEdge;
+ case QRhiSampler::Border:
+ return MTLSamplerAddressModeClampToBorderColor;
+ case QRhiSampler::Mirror:
+ return MTLSamplerAddressModeMirrorRepeat;
+ case QRhiSampler::MirrorOnce:
+ return MTLSamplerAddressModeMirrorClampToEdge;
+ default:
+ Q_UNREACHABLE();
+ return MTLSamplerAddressModeClampToEdge;
+ }
+}
+
+static inline MTLCompareFunction toMetalTextureCompareFunction(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return MTLCompareFunctionNever;
+ case QRhiSampler::Less:
+ return MTLCompareFunctionLess;
+ case QRhiSampler::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiSampler::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiSampler::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiSampler::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiSampler::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiSampler::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionNever;
+ }
+}
+
+bool QMetalSampler::build()
+{
+ if (d->samplerState)
+ release();
+
+ MTLSamplerDescriptor *desc = [[MTLSamplerDescriptor alloc] init];
+ desc.minFilter = toMetalFilter(m_minFilter);
+ desc.magFilter = toMetalFilter(m_magFilter);
+ desc.mipFilter = toMetalMipmapMode(m_mipmapMode);
+ desc.sAddressMode = toMetalAddressMode(m_addressU);
+ desc.tAddressMode = toMetalAddressMode(m_addressV);
+ desc.rAddressMode = toMetalAddressMode(m_addressW);
+ desc.compareFunction = toMetalTextureCompareFunction(m_compareOp);
+
+ QRHI_RES_RHI(QRhiMetal);
+ d->samplerState = [rhiD->d->dev newSamplerStateWithDescriptor: desc];
+ [desc release];
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here.
+// We do have MTLRenderPassDescriptor of course, but it will be created on the fly for each pass.
+QMetalRenderPassDescriptor::QMetalRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QMetalRenderPassDescriptor::~QMetalRenderPassDescriptor()
+{
+ release();
+}
+
+void QMetalRenderPassDescriptor::release()
+{
+ // nothing to do here
+}
+
+QMetalReferenceRenderTarget::QMetalReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalReferenceRenderTarget::~QMetalReferenceRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QMetalReferenceRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalReferenceRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalReferenceRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalTextureRenderTarget::QMetalTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(new QMetalRenderTargetData)
+{
+}
+
+QMetalTextureRenderTarget::~QMetalTextureRenderTarget()
+{
+ release();
+ delete d;
+}
+
+void QMetalTextureRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QRhiRenderPassDescriptor *QMetalTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = colorAttachments.count();
+ rpD->hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ for (int i = 0, ie = colorAttachments.count(); i != ie; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ rpD->colorFormat[i] = texD ? texD->d->format : rbD->d->format;
+ }
+
+ if (m_desc.depthTexture())
+ rpD->dsFormat = QRHI_RES(QMetalTexture, m_desc.depthTexture())->d->format;
+ else if (m_desc.depthStencilBuffer())
+ rpD->dsFormat = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer())->d->format;
+
+ return rpD;
+}
+
+bool QMetalTextureRenderTarget::build()
+{
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ d->colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d->colorAttCount; ++i) {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, colorAttachments[i].texture());
+ QMetalRenderBuffer *rbD = QRHI_RES(QMetalRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ id<MTLTexture> dst = nil;
+ if (texD) {
+ dst = texD->d->tex;
+ if (i == 0) {
+ d->pixelSize = texD->pixelSize();
+ d->sampleCount = texD->samples;
+ }
+ } else if (rbD) {
+ dst = rbD->d->tex;
+ if (i == 0) {
+ d->pixelSize = rbD->pixelSize();
+ d->sampleCount = rbD->samples;
+ }
+ }
+ QMetalRenderTargetData::ColorAtt colorAtt;
+ colorAtt.tex = dst;
+ colorAtt.layer = colorAttachments[i].layer();
+ colorAtt.level = colorAttachments[i].level();
+ QMetalTexture *resTexD = QRHI_RES(QMetalTexture, colorAttachments[i].resolveTexture());
+ colorAtt.resolveTex = resTexD ? resTexD->d->tex : nil;
+ colorAtt.resolveLayer = colorAttachments[i].resolveLayer();
+ colorAtt.resolveLevel = colorAttachments[i].resolveLevel();
+ d->fb.colorAtt[i] = colorAtt;
+ }
+ d->dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
+ d->fb.dsTex = depthTexD->d->tex;
+ d->fb.hasStencil = false;
+ d->fb.depthNeedsStore = true;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthTexD->pixelSize();
+ d->sampleCount = depthTexD->samples;
+ }
+ } else {
+ QMetalRenderBuffer *depthRbD = QRHI_RES(QMetalRenderBuffer, m_desc.depthStencilBuffer());
+ d->fb.dsTex = depthRbD->d->tex;
+ d->fb.hasStencil = true;
+ d->fb.depthNeedsStore = false;
+ if (d->colorAttCount == 0) {
+ d->pixelSize = depthRbD->pixelSize();
+ d->sampleCount = depthRbD->samples;
+ }
+ }
+ d->dsAttCount = 1;
+ } else {
+ d->dsAttCount = 0;
+ }
+
+ return true;
+}
+
+QSize QMetalTextureRenderTarget::pixelSize() const
+{
+ return d->pixelSize;
+}
+
+float QMetalTextureRenderTarget::devicePixelRatio() const
+{
+ return d->dpr;
+}
+
+int QMetalTextureRenderTarget::sampleCount() const
+{
+ return d->sampleCount;
+}
+
+QMetalShaderResourceBindings::QMetalShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QMetalShaderResourceBindings::~QMetalShaderResourceBindings()
+{
+ release();
+}
+
+void QMetalShaderResourceBindings::release()
+{
+ sortedBindings.clear();
+ maxBinding = -1;
+}
+
+bool QMetalShaderResourceBindings::build()
+{
+ if (!sortedBindings.isEmpty())
+ release();
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+ if (!sortedBindings.isEmpty())
+ maxBinding = QRhiShaderResourceBindingPrivate::get(&sortedBindings.last())->binding;
+ else
+ maxBinding = -1;
+
+ boundResourceData.resize(sortedBindings.count());
+
+ for (int i = 0, ie = sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&sortedBindings[i]);
+ QMetalShaderResourceBindings::BoundResourceData &bd(boundResourceData[i]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.ubuf.buf);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex);
+ QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler);
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex);
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QMetalBuffer *bufD = QRHI_RES(QMetalBuffer, b->u.sbuf.buf);
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ generation += 1;
+ return true;
+}
+
+QMetalGraphicsPipeline::QMetalGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi),
+ d(new QMetalGraphicsPipelineData)
+{
+}
+
+QMetalGraphicsPipeline::~QMetalGraphicsPipeline()
+{
+ release();
+ delete d;
+}
+
+void QMetalGraphicsPipeline::release()
+{
+ QRHI_RES_RHI(QRhiMetal);
+
+ if (!d->ps)
+ return;
+
+ if (d->ps) {
+ [d->ps release];
+ d->ps = nil;
+ }
+
+ if (d->ds) {
+ [d->ds release];
+ d->ds = nil;
+ }
+
+ if (d->vsFunc) {
+ [d->vsFunc release];
+ d->vsFunc = nil;
+ }
+ if (d->vsLib) {
+ [d->vsLib release];
+ d->vsLib = nil;
+ }
+
+ if (d->fsFunc) {
+ [d->fsFunc release];
+ d->fsFunc = nil;
+ }
+ if (d->fsLib) {
+ [d->fsLib release];
+ d->fsLib = nil;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return MTLVertexFormatFloat4;
+ case QRhiVertexInputAttribute::Float3:
+ return MTLVertexFormatFloat3;
+ case QRhiVertexInputAttribute::Float2:
+ return MTLVertexFormatFloat2;
+ case QRhiVertexInputAttribute::Float:
+ return MTLVertexFormatFloat;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return MTLVertexFormatUChar4Normalized;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return MTLVertexFormatUChar2Normalized;
+ case QRhiVertexInputAttribute::UNormByte:
+ if (@available(macOS 10.13, iOS 11.0, *))
+ return MTLVertexFormatUCharNormalized;
+ else
+ Q_UNREACHABLE();
+ default:
+ Q_UNREACHABLE();
+ return MTLVertexFormatFloat4;
+ }
+}
+
+static inline MTLBlendFactor toMetalBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return MTLBlendFactorZero;
+ case QRhiGraphicsPipeline::One:
+ return MTLBlendFactorOne;
+ case QRhiGraphicsPipeline::SrcColor:
+ return MTLBlendFactorSourceColor;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return MTLBlendFactorOneMinusSourceColor;
+ case QRhiGraphicsPipeline::DstColor:
+ return MTLBlendFactorDestinationColor;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return MTLBlendFactorOneMinusDestinationColor;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return MTLBlendFactorSourceAlpha;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return MTLBlendFactorOneMinusSourceAlpha;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return MTLBlendFactorDestinationAlpha;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return MTLBlendFactorOneMinusDestinationAlpha;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return MTLBlendFactorBlendColor;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return MTLBlendFactorBlendAlpha;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return MTLBlendFactorOneMinusBlendColor;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return MTLBlendFactorOneMinusBlendAlpha;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return MTLBlendFactorSourceAlphaSaturated;
+ case QRhiGraphicsPipeline::Src1Color:
+ return MTLBlendFactorSource1Color;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return MTLBlendFactorOneMinusSource1Color;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return MTLBlendFactorSource1Alpha;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return MTLBlendFactorOneMinusSource1Alpha;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendFactorZero;
+ }
+}
+
+static inline MTLBlendOperation toMetalBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return MTLBlendOperationAdd;
+ case QRhiGraphicsPipeline::Subtract:
+ return MTLBlendOperationSubtract;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return MTLBlendOperationReverseSubtract;
+ case QRhiGraphicsPipeline::Min:
+ return MTLBlendOperationMin;
+ case QRhiGraphicsPipeline::Max:
+ return MTLBlendOperationMax;
+ default:
+ Q_UNREACHABLE();
+ return MTLBlendOperationAdd;
+ }
+}
+
+static inline uint toMetalColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+ uint f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= MTLColorWriteMaskRed;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= MTLColorWriteMaskGreen;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= MTLColorWriteMaskBlue;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= MTLColorWriteMaskAlpha;
+ return f;
+}
+
+static inline MTLCompareFunction toMetalCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return MTLCompareFunctionNever;
+ case QRhiGraphicsPipeline::Less:
+ return MTLCompareFunctionLess;
+ case QRhiGraphicsPipeline::Equal:
+ return MTLCompareFunctionEqual;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return MTLCompareFunctionLessEqual;
+ case QRhiGraphicsPipeline::Greater:
+ return MTLCompareFunctionGreater;
+ case QRhiGraphicsPipeline::NotEqual:
+ return MTLCompareFunctionNotEqual;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return MTLCompareFunctionGreaterEqual;
+ case QRhiGraphicsPipeline::Always:
+ return MTLCompareFunctionAlways;
+ default:
+ Q_UNREACHABLE();
+ return MTLCompareFunctionAlways;
+ }
+}
+
+static inline MTLStencilOperation toMetalStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return MTLStencilOperationZero;
+ case QRhiGraphicsPipeline::Keep:
+ return MTLStencilOperationKeep;
+ case QRhiGraphicsPipeline::Replace:
+ return MTLStencilOperationReplace;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return MTLStencilOperationIncrementClamp;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return MTLStencilOperationDecrementClamp;
+ case QRhiGraphicsPipeline::Invert:
+ return MTLStencilOperationInvert;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return MTLStencilOperationIncrementWrap;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return MTLStencilOperationDecrementWrap;
+ default:
+ Q_UNREACHABLE();
+ return MTLStencilOperationKeep;
+ }
+}
+
+static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return MTLPrimitiveTypeTriangle;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return MTLPrimitiveTypeTriangleStrip;
+ case QRhiGraphicsPipeline::Lines:
+ return MTLPrimitiveTypeLine;
+ case QRhiGraphicsPipeline::LineStrip:
+ return MTLPrimitiveTypeLineStrip;
+ case QRhiGraphicsPipeline::Points:
+ return MTLPrimitiveTypePoint;
+ default:
+ Q_UNREACHABLE();
+ return MTLPrimitiveTypeTriangle;
+ }
+}
+
+static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return MTLCullModeNone;
+ case QRhiGraphicsPipeline::Front:
+ return MTLCullModeFront;
+ case QRhiGraphicsPipeline::Back:
+ return MTLCullModeBack;
+ default:
+ Q_UNREACHABLE();
+ return MTLCullModeNone;
+ }
+}
+
+id<MTLLibrary> QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant,
+ QString *error, QByteArray *entryPoint)
+{
+ QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant });
+ if (!mtllib.shader().isEmpty()) {
+ dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(),
+ mtllib.shader().size(),
+ dispatch_get_global_queue(0, 0),
+ DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithData: data error: &err];
+ dispatch_release(data);
+ if (!err) {
+ *entryPoint = mtllib.entryPoint();
+ return lib;
+ } else {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to load metallib from baked shader: %s", qPrintable(msg));
+ }
+ }
+
+ QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant });
+ if (mslSource.shader().isEmpty()) {
+ qWarning() << "No MSL 1.2 code found in baked shader" << shader;
+ return nil;
+ }
+
+ NSString *src = [NSString stringWithUTF8String: mslSource.shader().constData()];
+ MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
+ opts.languageVersion = MTLLanguageVersion1_2;
+ NSError *err = nil;
+ id<MTLLibrary> lib = [dev newLibraryWithSource: src options: opts error: &err];
+ [opts release];
+ // src is autoreleased
+
+ if (err) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ *error = msg;
+ return nil;
+ }
+
+ *entryPoint = mslSource.entryPoint();
+ return lib;
+}
+
+id<MTLFunction> QRhiMetalData::createMSLShaderFunction(id<MTLLibrary> lib, const QByteArray &entryPoint)
+{
+ NSString *name = [NSString stringWithUTF8String: entryPoint.constData()];
+ id<MTLFunction> f = [lib newFunctionWithName: name];
+ [name release];
+ return f;
+}
+
+bool QMetalGraphicsPipeline::build()
+{
+ if (d->ps)
+ release();
+
+ QRHI_RES_RHI(QRhiMetal);
+
+ // same binding space for vertex and constant buffers - work it around
+ const int firstVertexBinding = QRHI_RES(QMetalShaderResourceBindings, m_shaderResourceBindings)->maxBinding + 1;
+
+ MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ const int loc = attribute.location();
+ inputLayout.attributes[loc].format = toMetalAttributeFormat(attribute.format());
+ inputLayout.attributes[loc].offset = attribute.offset();
+ inputLayout.attributes[loc].bufferIndex = firstVertexBinding + attribute.binding();
+ }
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ for (int i = 0, ie = bindings.count(); i != ie; ++i) {
+ const QRhiVertexInputBinding &binding(bindings[i]);
+ const int layoutIdx = firstVertexBinding + i;
+ inputLayout.layouts[layoutIdx].stepFunction =
+ binding.classification() == QRhiVertexInputBinding::PerInstance
+ ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
+ inputLayout.layouts[layoutIdx].stepRate = binding.instanceStepRate();
+ inputLayout.layouts[layoutIdx].stride = binding.stride();
+ }
+
+ MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
+
+ rpDesc.vertexDescriptor = inputLayout;
+
+ // mutability cannot be determined (slotted buffers could be set as
+ // MTLMutabilityImmutable, but then we potentially need a different
+ // descriptor for each buffer combination as this depends on the actual
+ // buffers not just the resource binding layout) so leave it at the default
+
+ for (const QRhiShaderStage &shaderStage : qAsConst(m_shaderStages)) {
+ QString error;
+ QByteArray entryPoint;
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint);
+ if (!lib) {
+ qWarning("MSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ return false;
+ }
+ switch (shaderStage.type()) {
+ case QRhiShaderStage::Vertex:
+ rpDesc.vertexFunction = func;
+ d->vsLib = lib;
+ d->vsFunc = func;
+ break;
+ case QRhiShaderStage::Fragment:
+ rpDesc.fragmentFunction = func;
+ d->fsLib = lib;
+ d->fsFunc = func;
+ break;
+ default:
+ [func release];
+ [lib release];
+ break;
+ }
+ }
+
+ QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
+
+ if (rpD->colorAttachmentCount) {
+ // defaults when no targetBlends are provided
+ rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormat(rpD->colorFormat[0]);
+ rpDesc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
+ rpDesc.colorAttachments[0].blendingEnabled = false;
+
+ Q_ASSERT(m_targetBlends.count() == rpD->colorAttachmentCount
+ || (m_targetBlends.isEmpty() && rpD->colorAttachmentCount == 1));
+
+ for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+ const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+ rpDesc.colorAttachments[i].pixelFormat = MTLPixelFormat(rpD->colorFormat[i]);
+ rpDesc.colorAttachments[i].blendingEnabled = b.enable;
+ rpDesc.colorAttachments[i].sourceRGBBlendFactor = toMetalBlendFactor(b.srcColor);
+ rpDesc.colorAttachments[i].destinationRGBBlendFactor = toMetalBlendFactor(b.dstColor);
+ rpDesc.colorAttachments[i].rgbBlendOperation = toMetalBlendOp(b.opColor);
+ rpDesc.colorAttachments[i].sourceAlphaBlendFactor = toMetalBlendFactor(b.srcAlpha);
+ rpDesc.colorAttachments[i].destinationAlphaBlendFactor = toMetalBlendFactor(b.dstAlpha);
+ rpDesc.colorAttachments[i].alphaBlendOperation = toMetalBlendOp(b.opAlpha);
+ rpDesc.colorAttachments[i].writeMask = toMetalColorWriteMask(b.colorWrite);
+ }
+ }
+
+ if (rpD->hasDepthStencil) {
+ // Must only be set when a depth-stencil buffer will actually be bound,
+ // validation blows up otherwise.
+ MTLPixelFormat fmt = MTLPixelFormat(rpD->dsFormat);
+ rpDesc.depthAttachmentPixelFormat = fmt;
+ if (fmt != MTLPixelFormatDepth16Unorm && fmt != MTLPixelFormatDepth32Float)
+ rpDesc.stencilAttachmentPixelFormat = fmt;
+ }
+
+ rpDesc.sampleCount = rhiD->effectiveSampleCount(m_sampleCount);
+
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newRenderPipelineStateWithDescriptor: rpDesc error: &err];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ [rpDesc release];
+ return false;
+ }
+ [rpDesc release];
+
+ MTLDepthStencilDescriptor *dsDesc = [[MTLDepthStencilDescriptor alloc] init];
+ dsDesc.depthCompareFunction = m_depthTest ? toMetalCompareOp(m_depthOp) : MTLCompareFunctionAlways;
+ dsDesc.depthWriteEnabled = m_depthWrite;
+ if (m_stencilTest) {
+ dsDesc.frontFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.frontFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilFront.failOp);
+ dsDesc.frontFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilFront.depthFailOp);
+ dsDesc.frontFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilFront.passOp);
+ dsDesc.frontFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilFront.compareOp);
+ dsDesc.frontFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.frontFaceStencil.writeMask = m_stencilWriteMask;
+
+ dsDesc.backFaceStencil = [[MTLStencilDescriptor alloc] init];
+ dsDesc.backFaceStencil.stencilFailureOperation = toMetalStencilOp(m_stencilBack.failOp);
+ dsDesc.backFaceStencil.depthFailureOperation = toMetalStencilOp(m_stencilBack.depthFailOp);
+ dsDesc.backFaceStencil.depthStencilPassOperation = toMetalStencilOp(m_stencilBack.passOp);
+ dsDesc.backFaceStencil.stencilCompareFunction = toMetalCompareOp(m_stencilBack.compareOp);
+ dsDesc.backFaceStencil.readMask = m_stencilReadMask;
+ dsDesc.backFaceStencil.writeMask = m_stencilWriteMask;
+ }
+
+ d->ds = [rhiD->d->dev newDepthStencilStateWithDescriptor: dsDesc];
+ [dsDesc release];
+
+ d->primitiveType = toMetalPrimitiveType(m_topology);
+ d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
+ d->cullMode = toMetalCullMode(m_cullMode);
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalComputePipeline::QMetalComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi),
+ d(new QMetalComputePipelineData)
+{
+}
+
+QMetalComputePipeline::~QMetalComputePipeline()
+{
+ release();
+ delete d;
+}
+
+void QMetalComputePipeline::release()
+{
+ QRHI_RES_RHI(QRhiMetal);
+
+ if (d->csFunc) {
+ [d->csFunc release];
+ d->csFunc = nil;
+ }
+ if (d->csLib) {
+ [d->csLib release];
+ d->csLib = nil;
+ }
+
+ if (!d->ps)
+ return;
+
+ if (d->ps) {
+ [d->ps release];
+ d->ps = nil;
+ }
+
+ rhiD->unregisterResource(this);
+}
+
+bool QMetalComputePipeline::build()
+{
+ if (d->ps)
+ release();
+
+ QRHI_RES_RHI(QRhiMetal);
+
+ const QShader shader = m_shaderStage.shader();
+ QString error;
+ QByteArray entryPoint;
+ id<MTLLibrary> lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(),
+ &error, &entryPoint);
+ if (!lib) {
+ qWarning("MSL shader compilation failed: %s", qPrintable(error));
+ return false;
+ }
+ id<MTLFunction> func = rhiD->d->createMSLShaderFunction(lib, entryPoint);
+ if (!func) {
+ qWarning("MSL function for entry point %s not found", entryPoint.constData());
+ [lib release];
+ return false;
+ }
+ d->csLib = lib;
+ d->csFunc = func;
+ std::array<uint, 3> localSize = shader.description().computeShaderLocalSize();
+ d->localSize = MTLSizeMake(localSize[0], localSize[1], localSize[2]);
+
+ NSError *err = nil;
+ d->ps = [rhiD->d->dev newComputePipelineStateWithFunction: d->csFunc error: &err];
+ if (!d->ps) {
+ const QString msg = QString::fromNSString(err.localizedDescription);
+ qWarning("Failed to create render pipeline state: %s", qPrintable(msg));
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QMetalCommandBuffer::QMetalCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi),
+ d(new QMetalCommandBufferData)
+{
+ resetState();
+}
+
+QMetalCommandBuffer::~QMetalCommandBuffer()
+{
+ release();
+ delete d;
+}
+
+void QMetalCommandBuffer::release()
+{
+ // nothing to do here, we do not own the MTL cb object
+}
+
+const QRhiNativeHandles *QMetalCommandBuffer::nativeHandles()
+{
+ nativeHandlesStruct.commandBuffer = d->cb;
+ nativeHandlesStruct.encoder = d->currentRenderPassEncoder;
+ return &nativeHandlesStruct;
+}
+
+void QMetalCommandBuffer::resetState()
+{
+ d->currentRenderPassEncoder = nil;
+ d->currentComputePassEncoder = nil;
+ d->currentPassRpDesc = nil;
+ resetPerPassState();
+}
+
+void QMetalCommandBuffer::resetPerPassState()
+{
+ recordingPass = NoPass;
+ currentTarget = nullptr;
+ resetPerPassCachedState();
+}
+
+void QMetalCommandBuffer::resetPerPassCachedState()
+{
+ currentGraphicsPipeline = nullptr;
+ currentComputePipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentGraphicsSrb = nullptr;
+ currentComputeSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentResSlot = -1;
+ currentIndexBuffer = nullptr;
+
+ d->currentFirstVertexBinding = -1;
+ d->currentVertexInputsBuffers.clear();
+ d->currentVertexInputOffsets.clear();
+}
+
+QMetalSwapChain::QMetalSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rtWrapper(rhi),
+ cbWrapper(rhi),
+ d(new QMetalSwapChainData)
+{
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ d->sem[i] = nullptr;
+ d->msaaTex[i] = nil;
+ }
+}
+
+QMetalSwapChain::~QMetalSwapChain()
+{
+ release();
+ delete d;
+}
+
+void QMetalSwapChain::release()
+{
+ if (!d->layer)
+ return;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (d->sem[i]) {
+ // the semaphores cannot be released if they do not have the initial value
+ dispatch_semaphore_wait(d->sem[i], DISPATCH_TIME_FOREVER);
+ dispatch_semaphore_signal(d->sem[i]);
+
+ dispatch_release(d->sem[i]);
+ d->sem[i] = nullptr;
+ }
+ }
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = nil;
+ }
+
+ d->layer = nullptr;
+
+ QRHI_RES_RHI(QRhiMetal);
+ rhiD->swapchains.remove(this);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QMetalSwapChain::currentFrameCommandBuffer()
+{
+ return &cbWrapper;
+}
+
+QRhiRenderTarget *QMetalSwapChain::currentFrameRenderTarget()
+{
+ return &rtWrapper;
+}
+
+QSize QMetalSwapChain::surfacePixelSize()
+{
+ // may be called before build, must not access other than m_*
+
+ NSView *v = (NSView *) m_window->winId();
+ if (v) {
+ CAMetalLayer *layer = (CAMetalLayer *) [v layer];
+ if (layer) {
+ CGSize size = [layer drawableSize];
+ return QSize(size.width, size.height);
+ }
+ }
+ return QSize();
+}
+
+QRhiRenderPassDescriptor *QMetalSwapChain::newCompatibleRenderPassDescriptor()
+{
+ chooseFormats(); // ensure colorFormat and similar are filled out
+
+ QRHI_RES_RHI(QRhiMetal);
+ QMetalRenderPassDescriptor *rpD = new QMetalRenderPassDescriptor(m_rhi);
+ rpD->colorAttachmentCount = 1;
+ rpD->hasDepthStencil = m_depthStencil != nullptr;
+
+ rpD->colorFormat[0] = d->colorFormat;
+
+ // m_depthStencil may not be built yet so cannot rely on computed fields in it
+ rpD->dsFormat = rhiD->d->dev.depth24Stencil8PixelFormatSupported
+ ? MTLPixelFormatDepth24Unorm_Stencil8 : MTLPixelFormatDepth32Float_Stencil8;
+
+ return rpD;
+}
+
+void QMetalSwapChain::chooseFormats()
+{
+ QRHI_RES_RHI(QRhiMetal);
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ // pick a format that is allowed for CAMetalLayer.pixelFormat
+ d->colorFormat = m_flags.testFlag(sRGB) ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+ d->rhiColorFormat = QRhiTexture::BGRA8;
+}
+
+bool QMetalSwapChain::buildOrResize()
+{
+ Q_ASSERT(m_window);
+
+ const bool needsRegistration = !window || window != m_window;
+
+ if (window && window != m_window)
+ release();
+ // else no release(), this is intentional
+
+ QRHI_RES_RHI(QRhiMetal);
+ if (needsRegistration)
+ rhiD->swapchains.insert(this);
+
+ window = m_window;
+
+ if (window->surfaceType() != QSurface::MetalSurface) {
+ qWarning("QMetalSwapChain only supports MetalSurface windows");
+ return false;
+ }
+
+ NSView *v = (NSView *) window->winId();
+ d->layer = (CAMetalLayer *) [v layer];
+ Q_ASSERT(d->layer);
+
+ chooseFormats();
+ if (d->colorFormat != d->layer.pixelFormat)
+ d->layer.pixelFormat = d->colorFormat;
+
+ if (m_flags.testFlag(UsedAsTransferSource))
+ d->layer.framebufferOnly = NO;
+
+#ifdef Q_OS_MACOS
+ if (m_flags.testFlag(NoVSync)) {
+ if (@available(macOS 10.13, *))
+ d->layer.displaySyncEnabled = NO;
+ }
+#endif
+
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ [d->layer setDevice: rhiD->d->dev];
+
+ d->curDrawable = nil;
+
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ if (!d->sem[i])
+ d->sem[i] = dispatch_semaphore_create(QMTL_FRAMES_IN_FLIGHT - 1);
+ }
+
+ currentFrameSlot = 0;
+ frameCount = 0;
+
+ ds = m_depthStencil ? QRHI_RES(QMetalRenderBuffer, m_depthStencil) : nullptr;
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the layer size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ rtWrapper.d->pixelSize = pixelSize;
+ rtWrapper.d->dpr = window->devicePixelRatio();
+ rtWrapper.d->sampleCount = samples;
+ rtWrapper.d->colorAttCount = 1;
+ rtWrapper.d->dsAttCount = ds ? 1 : 0;
+
+ qDebug("got CAMetalLayer, size %dx%d", pixelSize.width(), pixelSize.height());
+
+ if (samples > 1) {
+ MTLTextureDescriptor *desc = [[MTLTextureDescriptor alloc] init];
+ desc.textureType = MTLTextureType2DMultisample;
+ desc.pixelFormat = d->colorFormat;
+ desc.width = pixelSize.width();
+ desc.height = pixelSize.height();
+ desc.sampleCount = samples;
+ desc.resourceOptions = MTLResourceStorageModePrivate;
+ desc.storageMode = MTLStorageModePrivate;
+ desc.usage = MTLTextureUsageRenderTarget;
+ for (int i = 0; i < QMTL_FRAMES_IN_FLIGHT; ++i) {
+ [d->msaaTex[i] release];
+ d->msaaTex[i] = [rhiD->d->dev newTextureWithDescriptor: desc];
+ }
+ [desc release];
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, QMTL_FRAMES_IN_FLIGHT, samples > 1 ? QMTL_FRAMES_IN_FLIGHT : 0, samples));
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h
new file mode 100644
index 0000000000..094801c58c
--- /dev/null
+++ b/src/gui/rhi/qrhimetal_p.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIMETAL_H
+#define QRHIMETAL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+// no Metal includes here, the user code may be plain C++
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiMetalInitParams : public QRhiInitParams
+{
+};
+
+struct Q_GUI_EXPORT QRhiMetalNativeHandles : public QRhiNativeHandles
+{
+ void *dev = nullptr; // id<MTLDevice>
+ void *cmdQueue = nullptr; // id<MTLCommandQueue>
+};
+
+struct Q_GUI_EXPORT QRhiMetalTextureNativeHandles : public QRhiNativeHandles
+{
+ void *texture = nullptr; // id<MTLTexture>
+};
+
+struct Q_GUI_EXPORT QRhiMetalCommandBufferNativeHandles : public QRhiNativeHandles
+{
+ void *commandBuffer = nullptr; // id<MTLCommandBuffer>
+ void *encoder = nullptr; // id<MTLRenderCommandEncoder>
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h
new file mode 100644
index 0000000000..8b0256991d
--- /dev/null
+++ b/src/gui/rhi/qrhimetal_p_p.h
@@ -0,0 +1,450 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIMETAL_P_H
+#define QRHIMETAL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhimetal_p.h"
+#include "qrhi_p_p.h"
+#include <QWindow>
+
+QT_BEGIN_NAMESPACE
+
+static const int QMTL_FRAMES_IN_FLIGHT = 2;
+
+// have to hide the ObjC stuff, this header cannot contain MTL* at all
+struct QMetalBufferData;
+
+struct QMetalBuffer : public QRhiBuffer
+{
+ QMetalBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QMetalBuffer();
+ void release() override;
+ bool build() override;
+
+ QMetalBufferData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+};
+
+struct QMetalRenderBufferData;
+
+struct QMetalRenderBuffer : public QRhiRenderBuffer
+{
+ QMetalRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QMetalRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ QMetalRenderBufferData *d;
+ int samples = 1;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+};
+
+struct QMetalTextureData;
+
+struct QMetalTexture : public QRhiTexture
+{
+ QMetalTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QMetalTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+
+ QMetalTextureData *d;
+ QRhiMetalTextureNativeHandles nativeHandlesStruct;
+ int mipLevelCount = 0;
+ int samples = 1;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+ friend struct QMetalTextureData;
+};
+
+struct QMetalSamplerData;
+
+struct QMetalSampler : public QRhiSampler
+{
+ QMetalSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QMetalSampler();
+ void release() override;
+ bool build() override;
+
+ QMetalSamplerData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+ friend struct QMetalShaderResourceBindings;
+};
+
+struct QMetalRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QMetalRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QMetalRenderPassDescriptor();
+ void release() override;
+
+ // there is no MTLRenderPassDescriptor here as one will be created for each pass in beginPass()
+
+ // but the things needed for the render pipeline descriptor have to be provided
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+ int colorAttachmentCount = 0;
+ bool hasDepthStencil = false;
+ int colorFormat[MAX_COLOR_ATTACHMENTS];
+ int dsFormat;
+};
+
+struct QMetalRenderTargetData;
+
+struct QMetalReferenceRenderTarget : public QRhiRenderTarget
+{
+ QMetalReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QMetalReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QMetalRenderTargetData *d;
+};
+
+struct QMetalTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QMetalTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QMetalTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QMetalRenderTargetData *d;
+ friend class QRhiMetal;
+};
+
+struct QMetalShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QMetalShaderResourceBindings(QRhiImplementation *rhi);
+ ~QMetalShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ int maxBinding = -1;
+
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundStorageImageData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundStorageBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ BoundStorageImageData simage;
+ BoundStorageBufferData sbuf;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData;
+
+ uint generation = 0;
+ friend class QRhiMetal;
+};
+
+struct QMetalGraphicsPipelineData;
+
+struct QMetalGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QMetalGraphicsPipeline(QRhiImplementation *rhi);
+ ~QMetalGraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ QMetalGraphicsPipelineData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+};
+
+struct QMetalComputePipelineData;
+
+struct QMetalComputePipeline : public QRhiComputePipeline
+{
+ QMetalComputePipeline(QRhiImplementation *rhi);
+ ~QMetalComputePipeline();
+ void release() override;
+ bool build() override;
+
+ QMetalComputePipelineData *d;
+ uint generation = 0;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiMetal;
+};
+
+struct QMetalCommandBufferData;
+struct QMetalSwapChain;
+
+struct QMetalCommandBuffer : public QRhiCommandBuffer
+{
+ QMetalCommandBuffer(QRhiImplementation *rhi);
+ ~QMetalCommandBuffer();
+ void release() override;
+
+ QMetalCommandBufferData *d = nullptr;
+ QRhiMetalCommandBufferNativeHandles nativeHandlesStruct;
+
+ enum PassType {
+ NoPass,
+ RenderPass,
+ ComputePass
+ };
+
+ PassType recordingPass;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentGraphicsPipeline;
+ QRhiComputePipeline *currentComputePipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentGraphicsSrb;
+ QRhiShaderResourceBindings *currentComputeSrb;
+ uint currentSrbGeneration;
+ int currentResSlot;
+ QRhiBuffer *currentIndexBuffer;
+ quint32 currentIndexOffset;
+ QRhiCommandBuffer::IndexFormat currentIndexFormat;
+
+ const QRhiNativeHandles *nativeHandles();
+ void resetState();
+ void resetPerPassState();
+ void resetPerPassCachedState();
+};
+
+struct QMetalSwapChainData;
+
+struct QMetalSwapChain : public QRhiSwapChain
+{
+ QMetalSwapChain(QRhiImplementation *rhi);
+ ~QMetalSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+
+ bool buildOrResize() override;
+
+ void chooseFormats();
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ int currentFrameSlot = 0; // 0..QMTL_FRAMES_IN_FLIGHT-1
+ int frameCount = 0;
+ int samples = 1;
+ QMetalReferenceRenderTarget rtWrapper;
+ QMetalCommandBuffer cbWrapper;
+ QMetalRenderBuffer *ds = nullptr;
+ QMetalSwapChainData *d = nullptr;
+};
+
+struct QRhiMetalData;
+
+class QRhiMetal : public QRhiImplementation
+{
+public:
+ QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice = nullptr);
+ ~QRhiMetal();
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiComputePipeline *createComputePipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+ void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+ void makeThreadLocalNativeContextCurrent() override;
+
+ void executeDeferredReleases(bool forced = false);
+ void finishActiveReadbacks(bool forced = false);
+ qsizetype subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const;
+ void enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEncPtr,
+ int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc,
+ qsizetype *curOfs);
+ void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD);
+ void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
+ bool offsetOnlyChange);
+ int effectiveSampleCount(int sampleCount) const;
+
+ bool importedDevice = false;
+ bool importedCmdQueue = false;
+ QMetalSwapChain *currentSwapChain = nullptr;
+ QSet<QMetalSwapChain *> swapchains;
+ QRhiMetalNativeHandles nativeHandlesStruct;
+
+ struct {
+ int maxTextureSize = 4096;
+ } caps;
+
+ QRhiMetalData *d = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
new file mode 100644
index 0000000000..1314e53893
--- /dev/null
+++ b/src/gui/rhi/qrhinull.cpp
@@ -0,0 +1,764 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhinull_p_p.h"
+#include <qmath.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhiNullInitParams
+ \inmodule QtRhi
+ \brief Null backend specific initialization parameters.
+
+ A Null QRhi needs no special parameters for initialization.
+
+ \badcode
+ QRhiNullInitParams params;
+ rhi = QRhi::create(QRhi::Null, &params);
+ \endcode
+
+ The Null backend does not issue any graphics calls and creates no
+ resources. All QRhi operations will succeed as normal so applications can
+ still be run, albeit potentially at an unthrottled speed, depending on
+ their frame rendering strategy. The backend reports resources to
+ QRhiProfiler as usual.
+ */
+
+/*!
+ \class QRhiNullNativeHandles
+ \inmodule QtRhi
+ \brief Empty.
+ */
+
+/*!
+ \class QRhiNullTextureNativeHandles
+ \inmodule QtRhi
+ \brief Empty.
+ */
+
+QRhiNull::QRhiNull(QRhiNullInitParams *params)
+ : offscreenCommandBuffer(this)
+{
+ Q_UNUSED(params);
+}
+
+bool QRhiNull::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ return true;
+}
+
+void QRhiNull::destroy()
+{
+}
+
+QVector<int> QRhiNull::supportedSampleCounts() const
+{
+ return { 1 };
+}
+
+QRhiSwapChain *QRhiNull::createSwapChain()
+{
+ return new QNullSwapChain(this);
+}
+
+QRhiBuffer *QRhiNull::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QNullBuffer(this, type, usage, size);
+}
+
+int QRhiNull::ubufAlignment() const
+{
+ return 256;
+}
+
+bool QRhiNull::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiNull::isYUpInNDC() const
+{
+ return true;
+}
+
+bool QRhiNull::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiNull::clipSpaceCorrMatrix() const
+{
+ return QMatrix4x4(); // identity
+}
+
+bool QRhiNull::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ Q_UNUSED(format);
+ Q_UNUSED(flags);
+ return true;
+}
+
+bool QRhiNull::isFeatureSupported(QRhi::Feature feature) const
+{
+ Q_UNUSED(feature);
+ return true;
+}
+
+int QRhiNull::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return 16384;
+ case QRhi::MaxColorAttachments:
+ return 8;
+ case QRhi::FramesInFlight:
+ return 2; // dummy
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiNull::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiNull::sendVMemStatsToProfiler()
+{
+ // nothing to do here
+}
+
+void QRhiNull::makeThreadLocalNativeContextCurrent()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiNull::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QNullRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiNull::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QNullTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiNull::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QNullSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiNull::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QNullTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiNull::createGraphicsPipeline()
+{
+ return new QNullGraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiNull::createComputePipeline()
+{
+ return new QNullComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiNull::createShaderResourceBindings()
+{
+ return new QNullShaderResourceBindings(this);
+}
+
+void QRhiNull::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(ps);
+}
+
+void QRhiNull::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(srb);
+ Q_UNUSED(dynamicOffsetCount);
+ Q_UNUSED(dynamicOffsets);
+}
+
+void QRhiNull::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(startBinding);
+ Q_UNUSED(bindingCount);
+ Q_UNUSED(bindings);
+ Q_UNUSED(indexBuf);
+ Q_UNUSED(indexOffset);
+ Q_UNUSED(indexFormat);
+}
+
+void QRhiNull::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(viewport);
+}
+
+void QRhiNull::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(scissor);
+}
+
+void QRhiNull::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(c);
+}
+
+void QRhiNull::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(refValue);
+}
+
+void QRhiNull::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(vertexCount);
+ Q_UNUSED(instanceCount);
+ Q_UNUSED(firstVertex);
+ Q_UNUSED(firstInstance);
+}
+
+void QRhiNull::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(indexCount);
+ Q_UNUSED(instanceCount);
+ Q_UNUSED(firstIndex);
+ Q_UNUSED(vertexOffset);
+ Q_UNUSED(firstInstance);
+}
+
+void QRhiNull::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(name);
+}
+
+void QRhiNull::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiNull::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(msg);
+}
+
+void QRhiNull::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(ps);
+}
+
+void QRhiNull::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ Q_UNUSED(cb);
+ Q_UNUSED(x);
+ Q_UNUSED(y);
+ Q_UNUSED(z);
+}
+
+const QRhiNativeHandles *QRhiNull::nativeHandles(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+ return nullptr;
+}
+
+void QRhiNull::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiNull::endExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+QRhi::FrameOpResult QRhiNull::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ currentSwapChain = swapChain;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ QNullSwapChain *swapChainD = QRHI_RES(QNullSwapChain, swapChain);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, 0.000666f));
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ *cb = &offscreenCommandBuffer;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::endOffscreenFrame()
+{
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiNull::finish()
+{
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiNull::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_UNUSED(cb);
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ QRhiReadbackResult *result = u.read.result;
+ QRhiTexture *tex = u.read.rb.texture();
+ if (tex) {
+ result->format = tex->format();
+ result->pixelSize = q->sizeForMipLevel(u.read.rb.level(), tex->pixelSize());
+ } else {
+ Q_ASSERT(currentSwapChain);
+ result->format = QRhiTexture::RGBA8;
+ result->pixelSize = currentSwapChain->currentPixelSize();
+ }
+ quint32 byteSize = 0;
+ textureFormatInfo(result->format, result->pixelSize, nullptr, &byteSize);
+ result->data.fill(0, byteSize);
+ if (result->completed)
+ result->completed();
+ }
+ }
+ ud->free();
+}
+
+void QRhiNull::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ Q_UNUSED(rt);
+ Q_UNUSED(colorClearValue);
+ Q_UNUSED(depthStencilClearValue);
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+void QRhiNull::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+void QRhiNull::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+void QRhiNull::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ if (resourceUpdates)
+ resourceUpdate(cb, resourceUpdates);
+}
+
+QNullBuffer::QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QNullBuffer::~QNullBuffer()
+{
+ release();
+}
+
+void QNullBuffer::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+}
+
+bool QNullBuffer::build()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, m_size, 1, 0));
+ return true;
+}
+
+QNullRenderBuffer::QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QNullRenderBuffer::~QNullRenderBuffer()
+{
+ release();
+}
+
+void QNullRenderBuffer::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+}
+
+bool QNullRenderBuffer::build()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, 1));
+ return true;
+}
+
+QRhiTexture::Format QNullRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QNullTexture::QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+}
+
+QNullTexture::~QNullTexture()
+{
+ release();
+}
+
+void QNullTexture::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+}
+
+bool QNullTexture::build()
+{
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, 1));
+ return true;
+}
+
+bool QNullTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ Q_UNUSED(src);
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+ QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const int mipLevelCount = hasMipMaps ? qCeil(log2(qMax(size.width(), size.height()))) + 1 : 1;
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, isCube ? 6 : 1, 1));
+ return true;
+}
+
+const QRhiNativeHandles *QNullTexture::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+QNullSampler::QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QNullSampler::~QNullSampler()
+{
+ release();
+}
+
+void QNullSampler::release()
+{
+}
+
+bool QNullSampler::build()
+{
+ return true;
+}
+
+QNullRenderPassDescriptor::QNullRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QNullRenderPassDescriptor::~QNullRenderPassDescriptor()
+{
+ release();
+}
+
+void QNullRenderPassDescriptor::release()
+{
+}
+
+QNullReferenceRenderTarget::QNullReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi),
+ d(rhi)
+{
+}
+
+QNullReferenceRenderTarget::~QNullReferenceRenderTarget()
+{
+ release();
+}
+
+void QNullReferenceRenderTarget::release()
+{
+}
+
+QSize QNullReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QNullReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QNullReferenceRenderTarget::sampleCount() const
+{
+ return 1;
+}
+
+QNullTextureRenderTarget::QNullTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags),
+ d(rhi)
+{
+}
+
+QNullTextureRenderTarget::~QNullTextureRenderTarget()
+{
+ release();
+}
+
+void QNullTextureRenderTarget::release()
+{
+}
+
+QRhiRenderPassDescriptor *QNullTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ return new QNullRenderPassDescriptor(m_rhi);
+}
+
+bool QNullTextureRenderTarget::build()
+{
+ d.rp = QRHI_RES(QNullRenderPassDescriptor, m_renderPassDesc);
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ if (!colorAttachments.isEmpty()) {
+ QRhiTexture *tex = colorAttachments.first().texture();
+ QRhiRenderBuffer *rb = colorAttachments.first().renderBuffer();
+ d.pixelSize = tex ? tex->pixelSize() : rb->pixelSize();
+ } else if (m_desc.depthStencilBuffer()) {
+ d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+ } else if (m_desc.depthTexture()) {
+ d.pixelSize = m_desc.depthTexture()->pixelSize();
+ }
+ return true;
+}
+
+QSize QNullTextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QNullTextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QNullTextureRenderTarget::sampleCount() const
+{
+ return 1;
+}
+
+QNullShaderResourceBindings::QNullShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QNullShaderResourceBindings::~QNullShaderResourceBindings()
+{
+ release();
+}
+
+void QNullShaderResourceBindings::release()
+{
+}
+
+bool QNullShaderResourceBindings::build()
+{
+ return true;
+}
+
+QNullGraphicsPipeline::QNullGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QNullGraphicsPipeline::~QNullGraphicsPipeline()
+{
+ release();
+}
+
+void QNullGraphicsPipeline::release()
+{
+}
+
+bool QNullGraphicsPipeline::build()
+{
+ return true;
+}
+
+QNullComputePipeline::QNullComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi)
+{
+}
+
+QNullComputePipeline::~QNullComputePipeline()
+{
+ release();
+}
+
+void QNullComputePipeline::release()
+{
+}
+
+bool QNullComputePipeline::build()
+{
+ return true;
+}
+
+QNullCommandBuffer::QNullCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+}
+
+QNullCommandBuffer::~QNullCommandBuffer()
+{
+ release();
+}
+
+void QNullCommandBuffer::release()
+{
+ // nothing to do here
+}
+
+QNullSwapChain::QNullSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rt(rhi),
+ cb(rhi)
+{
+}
+
+QNullSwapChain::~QNullSwapChain()
+{
+ release();
+}
+
+void QNullSwapChain::release()
+{
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+}
+
+QRhiCommandBuffer *QNullSwapChain::currentFrameCommandBuffer()
+{
+ return &cb;
+}
+
+QRhiRenderTarget *QNullSwapChain::currentFrameRenderTarget()
+{
+ return &rt;
+}
+
+QSize QNullSwapChain::surfacePixelSize()
+{
+ return QSize(1280, 720);
+}
+
+QRhiRenderPassDescriptor *QNullSwapChain::newCompatibleRenderPassDescriptor()
+{
+ return new QNullRenderPassDescriptor(m_rhi);
+}
+
+bool QNullSwapChain::buildOrResize()
+{
+ m_currentPixelSize = surfacePixelSize();
+ rt.d.rp = QRHI_RES(QNullRenderPassDescriptor, m_renderPassDesc);
+ rt.d.pixelSize = m_currentPixelSize;
+ frameCount = 0;
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, 1, 0, 1));
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhinull_p.h b/src/gui/rhi/qrhinull_p.h
new file mode 100644
index 0000000000..7d3ce5dbf1
--- /dev/null
+++ b/src/gui/rhi/qrhinull_p.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHINULL_H
+#define QRHINULL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiNullInitParams : public QRhiInitParams
+{
+};
+
+struct Q_GUI_EXPORT QRhiNullNativeHandles : public QRhiNativeHandles
+{
+};
+
+struct Q_GUI_EXPORT QRhiNullTextureNativeHandles : public QRhiNativeHandles
+{
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhinull_p_p.h b/src/gui/rhi/qrhinull_p_p.h
new file mode 100644
index 0000000000..b0227bc110
--- /dev/null
+++ b/src/gui/rhi/qrhinull_p_p.h
@@ -0,0 +1,294 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHINULL_P_H
+#define QRHINULL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhinull_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QNullBuffer : public QRhiBuffer
+{
+ QNullBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QNullBuffer();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullRenderBuffer : public QRhiRenderBuffer
+{
+ QNullRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags);
+ ~QNullRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+};
+
+struct QNullTexture : public QRhiTexture
+{
+ QNullTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QNullTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ QRhiNullTextureNativeHandles nativeHandlesStruct;
+};
+
+struct QNullSampler : public QRhiSampler
+{
+ QNullSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QNullSampler();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QNullRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QNullRenderPassDescriptor();
+ void release() override;
+};
+
+struct QNullRenderTargetData
+{
+ QNullRenderTargetData(QRhiImplementation *) { }
+
+ QNullRenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+};
+
+struct QNullReferenceRenderTarget : public QRhiRenderTarget
+{
+ QNullReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QNullReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QNullRenderTargetData d;
+};
+
+struct QNullTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QNullTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QNullTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QNullRenderTargetData d;
+};
+
+struct QNullShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QNullShaderResourceBindings(QRhiImplementation *rhi);
+ ~QNullShaderResourceBindings();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QNullGraphicsPipeline(QRhiImplementation *rhi);
+ ~QNullGraphicsPipeline();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullComputePipeline : public QRhiComputePipeline
+{
+ QNullComputePipeline(QRhiImplementation *rhi);
+ ~QNullComputePipeline();
+ void release() override;
+ bool build() override;
+};
+
+struct QNullCommandBuffer : public QRhiCommandBuffer
+{
+ QNullCommandBuffer(QRhiImplementation *rhi);
+ ~QNullCommandBuffer();
+ void release() override;
+};
+
+struct QNullSwapChain : public QRhiSwapChain
+{
+ QNullSwapChain(QRhiImplementation *rhi);
+ ~QNullSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ QNullReferenceRenderTarget rt;
+ QNullCommandBuffer cb;
+ int frameCount = 0;
+};
+
+class QRhiNull : public QRhiImplementation
+{
+public:
+ QRhiNull(QRhiNullInitParams *params);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiComputePipeline *createComputePipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+ void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+ void makeThreadLocalNativeContextCurrent() override;
+
+ QRhiNullNativeHandles nativeHandlesStruct;
+ QRhiSwapChain *currentSwapChain = nullptr;
+ QNullCommandBuffer offscreenCommandBuffer;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhiprofiler.cpp b/src/gui/rhi/qrhiprofiler.cpp
new file mode 100644
index 0000000000..15e3007d49
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler.cpp
@@ -0,0 +1,603 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhiprofiler_p_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhiProfiler
+ \inmodule QtRhi
+
+ \brief Collects resource and timing information from an active QRhi.
+
+ A QRhiProfiler is present for each QRhi. Query it via QRhi::profiler(). The
+ profiler is active only when the QRhi was created with
+ QRhi::EnableProfiling. No data is collected otherwise.
+
+ \note GPU timings are only available when QRhi indicates that
+ QRhi::Timestamps is supported.
+
+ Besides collecting data from the QRhi implementations, some additional
+ values are calculated. For example, for textures and similar resources the
+ profiler gives an estimate of the complete amount of memory the resource
+ needs.
+
+ \section2 Output Format
+
+ The output is comma-separated text. Each line has a number of
+ comma-separated entries and each line ends with a comma.
+
+ For example:
+
+ \badcode
+ 1,0,140446057946208,Triangle vbuf,type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,0,140446057947376,Triangle ubuf,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 1,1,140446057950416,,type,0,usage,1,logical_size,112,effective_size,112,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057950544,,type,0,usage,2,logical_size,12,effective_size,12,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057947440,,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 1,1,140446057984784,Cube vbuf (textured),type,0,usage,1,logical_size,720,effective_size,720,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,1,140446057982528,Cube ubuf (textured),type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 7,8,140446058913648,Qt texture,width,256,height,256,format,1,owns_native_resource,1,mip_count,9,layer_count,1,effective_sample_count,1,approx_byte_size,349524,
+ 1,8,140446058795856,Cube vbuf (textured with offscreen),type,0,usage,1,logical_size,720,effective_size,720,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,8,140446058947920,Cube ubuf (textured with offscreen),type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 7,8,140446058794928,Texture for offscreen content,width,512,height,512,format,1,owns_native_resource,1,mip_count,1,layer_count,1,effective_sample_count,1,approx_byte_size,1048576,
+ 1,8,140446058963904,Triangle vbuf,type,0,usage,1,logical_size,84,effective_size,84,backing_gpu_buf_count,1,backing_cpu_buf_count,0,
+ 1,8,140446058964560,Triangle ubuf,type,2,usage,4,logical_size,68,effective_size,256,backing_gpu_buf_count,2,backing_cpu_buf_count,0,
+ 5,9,140446057945392,,type,0,width,1280,height,720,effective_sample_count,1,transient_backing,0,winsys_backing,0,approx_byte_size,3686400,
+ 11,9,140446057944592,,width,1280,height,720,buffer_count,2,msaa_buffer_count,0,effective_sample_count,1,approx_total_byte_size,7372800,
+ 9,9,140446058913648,Qt texture,slot,0,size,262144,
+ 10,9,140446058913648,Qt texture,slot,0,
+ 17,2019,140446057944592,,frames_since_resize,121,min_ms_frame_delta,9,max_ms_frame_delta,33,Favg_ms_frame_delta,16.1167,
+ 18,2019,140446057944592,,frames_since_resize,121,min_ms_frame_build,0,max_ms_frame_build,1,Favg_ms_frame_build,0.00833333,
+ 17,4019,140446057944592,,frames_since_resize,241,min_ms_frame_delta,15,max_ms_frame_delta,17,Favg_ms_frame_delta,16.0583,
+ 18,4019,140446057944592,,frames_since_resize,241,min_ms_frame_build,0,max_ms_frame_build,0,Favg_ms_frame_build,0,
+ 12,5070,140446057944592,,
+ 2,5079,140446057947376,Triangle ubuf,
+ 2,5079,140446057946208,Triangle vbuf,
+ 2,5079,140446057947440,,
+ 2,5079,140446057950544,,
+ 2,5079,140446057950416,,
+ 8,5079,140446058913648,Qt texture,
+ 2,5079,140446057982528,Cube ubuf (textured),
+ 2,5079,140446057984784,Cube vbuf (textured),
+ 2,5079,140446058964560,Triangle ubuf,
+ 2,5079,140446058963904,Triangle vbuf,
+ 8,5079,140446058794928,Texture for offscreen content,
+ 2,5079,140446058947920,Cube ubuf (textured with offscreen),
+ 2,5079,140446058795856,Cube vbuf (textured with offscreen),
+ 6,5079,140446057945392,,
+ \endcode
+
+ Each line starts with \c op, \c timestamp, \c res, \c name where op is a
+ value from StreamOp, timestamp is a recording timestamp in milliseconds
+ (qint64), res is a number (quint64) referring to the QRhiResource the entry
+ refers to, or 0 if not applicable. \c name is the value of
+ QRhiResource::name() and may be empty as well. The \c name will never
+ contain a comma.
+
+ This is followed by any number of \c{key, value} pairs where \c key is an
+ unspecified string and \c value is a number. If \c key starts with \c F, it
+ indicates the value is a float. Otherwise assume that the value is a
+ qint64.
+ */
+
+/*!
+ \enum QRhiProfiler::StreamOp
+ Describes an entry in the profiler's output stream.
+
+ \value NewBuffer A buffer is created
+ \value ReleaseBuffer A buffer is destroyed
+ \value NewBufferStagingArea A staging buffer for buffer upload is created
+ \value ReleaseBufferStagingArea A staging buffer for buffer upload is destroyed
+ \value NewRenderBuffer A renderbuffer is created
+ \value ReleaseRenderBuffer A renderbuffer is destroyed
+ \value NewTexture A texture is created
+ \value ReleaseTexture A texture is destroyed
+ \value NewTextureStagingArea A staging buffer for texture upload is created
+ \value ReleaseTextureStagingArea A staging buffer for texture upload is destroyed
+ \value ResizeSwapChain A swapchain is created or resized
+ \value ReleaseSwapChain A swapchain is destroyed
+ \value NewReadbackBuffer A staging buffer for readback is created
+ \value ReleaseReadbackBuffer A staging buffer for readback is destroyed
+ \value GpuMemAllocStats GPU memory allocator statistics
+ \value GpuFrameTime GPU frame times
+ \value FrameToFrameTime CPU frame-to-frame times
+ \value FrameBuildTime CPU beginFrame-endFrame times
+ */
+
+/*!
+ \class QRhiProfiler::CpuTime
+ \inmodule QtRhi
+ \brief Contains CPU-side frame timings.
+
+ Once sufficient number of frames have been rendered, the minimum, maximum,
+ and average values (in milliseconds) from various measurements are made
+ available in this struct queriable from QRhiProfiler::frameToFrameTimes()
+ and QRhiProfiler::frameBuildTimes().
+
+ \sa QRhiProfiler::setFrameTimingWriteInterval()
+ */
+
+/*!
+ \class QRhiProfiler::GpuTime
+ \inmodule QtRhi
+ \brief Contains GPU-side frame timings.
+
+ Once sufficient number of frames have been rendered, the minimum, maximum,
+ and average values (in milliseconds) calculated from GPU command buffer
+ timestamps are made available in this struct queriable from
+ QRhiProfiler::gpuFrameTimes().
+
+ \sa QRhiProfiler::setFrameTimingWriteInterval()
+ */
+
+/*!
+ \internal
+ */
+QRhiProfiler::QRhiProfiler()
+ : d(new QRhiProfilerPrivate)
+{
+ d->ts.start();
+}
+
+/*!
+ Destructor.
+ */
+QRhiProfiler::~QRhiProfiler()
+{
+ // Flush because there is a high chance we have writes that were made since
+ // the event loop last ran. (esp. relevant for network devices like QTcpSocket)
+ if (d->outputDevice)
+ d->outputDevice->waitForBytesWritten(1000);
+
+ delete d;
+}
+
+/*!
+ Sets the output \a device.
+
+ \note No output will be generated when QRhi::EnableProfiling was not set.
+ */
+void QRhiProfiler::setDevice(QIODevice *device)
+{
+ d->outputDevice = device;
+}
+
+/*!
+ Requests writing a GpuMemAllocStats entry into the output, when applicable.
+ Backends that do not support this will ignore the request. This is an
+ explicit request since getting the allocator status and statistics may be
+ an expensive operation.
+ */
+void QRhiProfiler::addVMemAllocatorStats()
+{
+ if (d->rhiDWhenEnabled)
+ d->rhiDWhenEnabled->sendVMemStatsToProfiler();
+}
+
+/*!
+ \return the currently set frame timing writeout interval.
+ */
+int QRhiProfiler::frameTimingWriteInterval() const
+{
+ return d->frameTimingWriteInterval;
+}
+
+/*!
+ Sets the number of frames that need to be rendered before the collected CPU
+ and GPU timings are processed (min, max, average are calculated) to \a
+ frameCount.
+
+ The default value is 120.
+ */
+void QRhiProfiler::setFrameTimingWriteInterval(int frameCount)
+{
+ if (frameCount > 0)
+ d->frameTimingWriteInterval = frameCount;
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the time that elapsed between two
+ QRhi::endFrame() calls.
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+ */
+QRhiProfiler::CpuTime QRhiProfiler::frameToFrameTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->frameToFrameTime;
+
+ return QRhiProfiler::CpuTime();
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the time that elapsed between
+ a QRhi::beginFrame() and QRhi::endFrame().
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+ */
+QRhiProfiler::CpuTime QRhiProfiler::frameBuildTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->beginToEndFrameTime;
+
+ return QRhiProfiler::CpuTime();
+}
+
+/*!
+ \return min, max, and avg in milliseconds for the GPU time that is spent on
+ one frame.
+
+ \note The values are all 0 until at least frameTimingWriteInterval() frames
+ have been rendered.
+
+ The GPU times should only be compared between runs on the same GPU of the
+ same system with the same backend. Comparing times for different graphics
+ cards or for different backends can give misleading results. The numbers are
+ not meant to be comparable that way.
+
+ \note Some backends have no support for this, and even for those that have,
+ it is not guaranteed that the driver will support it at run time. Support
+ can be checked via QRhi::Timestamps.
+ */
+QRhiProfiler::GpuTime QRhiProfiler::gpuFrameTimes(QRhiSwapChain *sc) const
+{
+ auto it = d->swapchains.constFind(sc);
+ if (it != d->swapchains.constEnd())
+ return it->gpuFrameTime;
+
+ return QRhiProfiler::GpuTime();
+}
+
+void QRhiProfilerPrivate::startEntry(QRhiProfiler::StreamOp op, qint64 timestamp, QRhiResource *res)
+{
+ buf.clear();
+ buf.append(QByteArray::number(op));
+ buf.append(',');
+ buf.append(QByteArray::number(timestamp));
+ buf.append(',');
+ buf.append(QByteArray::number(quint64(quintptr(res))));
+ buf.append(',');
+ if (res)
+ buf.append(res->name());
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::writeInt(const char *key, qint64 v)
+{
+ Q_ASSERT(key[0] != 'F');
+ buf.append(key);
+ buf.append(',');
+ buf.append(QByteArray::number(v));
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::writeFloat(const char *key, float f)
+{
+ Q_ASSERT(key[0] == 'F');
+ buf.append(key);
+ buf.append(',');
+ buf.append(QByteArray::number(f));
+ buf.append(',');
+}
+
+void QRhiProfilerPrivate::endEntry()
+{
+ buf.append('\n');
+ outputDevice->write(buf);
+}
+
+void QRhiProfilerPrivate::newBuffer(QRhiBuffer *buf, quint32 realSize, int backingGpuBufCount, int backingCpuBufCount)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewBuffer, ts.elapsed(), buf);
+ writeInt("type", buf->type());
+ writeInt("usage", buf->usage());
+ writeInt("logical_size", buf->size());
+ writeInt("effective_size", realSize);
+ writeInt("backing_gpu_buf_count", backingGpuBufCount);
+ writeInt("backing_cpu_buf_count", backingCpuBufCount);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseBuffer(QRhiBuffer *buf)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseBuffer, ts.elapsed(), buf);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newBufferStagingArea(QRhiBuffer *buf, int slot, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewBufferStagingArea, ts.elapsed(), buf);
+ writeInt("slot", slot);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseBufferStagingArea(QRhiBuffer *buf, int slot)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseBufferStagingArea, ts.elapsed(), buf);
+ writeInt("slot", slot);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newRenderBuffer(QRhiRenderBuffer *rb, bool transientBacking, bool winSysBacking, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QRhiRenderBuffer::Type type = rb->type();
+ const QSize sz = rb->pixelSize();
+ // just make up something, ds is likely D24S8 while color is RGBA8 or similar
+ const QRhiTexture::Format assumedFormat = type == QRhiRenderBuffer::DepthStencil ? QRhiTexture::D32F : QRhiTexture::RGBA8;
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(assumedFormat, sz, 1, 1);
+ if (sampleCount > 1)
+ byteSize *= sampleCount;
+
+ startEntry(QRhiProfiler::NewRenderBuffer, ts.elapsed(), rb);
+ writeInt("type", type);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("transient_backing", transientBacking);
+ writeInt("winsys_backing", winSysBacking);
+ writeInt("approx_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseRenderBuffer(QRhiRenderBuffer *rb)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseRenderBuffer, ts.elapsed(), rb);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newTexture(QRhiTexture *tex, bool owns, int mipCount, int layerCount, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QRhiTexture::Format format = tex->format();
+ const QSize sz = tex->pixelSize();
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(format, sz, mipCount, layerCount);
+ if (sampleCount > 1)
+ byteSize *= sampleCount;
+
+ startEntry(QRhiProfiler::NewTexture, ts.elapsed(), tex);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("format", format);
+ writeInt("owns_native_resource", owns);
+ writeInt("mip_count", mipCount);
+ writeInt("layer_count", layerCount);
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("approx_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseTexture(QRhiTexture *tex)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseTexture, ts.elapsed(), tex);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::newTextureStagingArea(QRhiTexture *tex, int slot, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewTextureStagingArea, ts.elapsed(), tex);
+ writeInt("slot", slot);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseTextureStagingArea(QRhiTexture *tex, int slot)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseTextureStagingArea, ts.elapsed(), tex);
+ writeInt("slot", slot);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::resizeSwapChain(QRhiSwapChain *sc, int bufferCount, int msaaBufferCount, int sampleCount)
+{
+ if (!outputDevice)
+ return;
+
+ const QSize sz = sc->currentPixelSize();
+ quint32 byteSize = rhiDWhenEnabled->approxByteSizeForTexture(QRhiTexture::BGRA8, sz, 1, 1);
+ byteSize = byteSize * bufferCount + byteSize * msaaBufferCount * sampleCount;
+
+ startEntry(QRhiProfiler::ResizeSwapChain, ts.elapsed(), sc);
+ writeInt("width", sz.width());
+ writeInt("height", sz.height());
+ writeInt("buffer_count", bufferCount);
+ writeInt("msaa_buffer_count", msaaBufferCount);
+ writeInt("effective_sample_count", sampleCount);
+ writeInt("approx_total_byte_size", byteSize);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseSwapChain(QRhiSwapChain *sc)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseSwapChain, ts.elapsed(), sc);
+ endEntry();
+}
+
+template<typename T>
+void calcTiming(QVector<T> *vec, T *minDelta, T *maxDelta, float *avgDelta)
+{
+ if (vec->isEmpty())
+ return;
+
+ *minDelta = *maxDelta = 0;
+ float totalDelta = 0;
+ for (T delta : qAsConst(*vec)) {
+ totalDelta += float(delta);
+ if (*minDelta == 0 || delta < *minDelta)
+ *minDelta = delta;
+ if (*maxDelta == 0 || delta > *maxDelta)
+ *maxDelta = delta;
+ }
+ *avgDelta = totalDelta / vec->count();
+
+ vec->clear();
+}
+
+void QRhiProfilerPrivate::beginSwapChainFrame(QRhiSwapChain *sc)
+{
+ Sc &scd(swapchains[sc]);
+ scd.beginToEndTimer.start();
+}
+
+void QRhiProfilerPrivate::endSwapChainFrame(QRhiSwapChain *sc, int frameCount)
+{
+ Sc &scd(swapchains[sc]);
+ if (!scd.frameToFrameRunning) {
+ scd.frameToFrameTimer.start();
+ scd.frameToFrameRunning = true;
+ return;
+ }
+
+ scd.frameToFrameSamples.append(scd.frameToFrameTimer.restart());
+ if (scd.frameToFrameSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.frameToFrameSamples,
+ &scd.frameToFrameTime.minTime, &scd.frameToFrameTime.maxTime, &scd.frameToFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::FrameToFrameTime, ts.elapsed(), sc);
+ writeInt("frames_since_resize", frameCount);
+ writeInt("min_ms_frame_delta", scd.frameToFrameTime.minTime);
+ writeInt("max_ms_frame_delta", scd.frameToFrameTime.maxTime);
+ writeFloat("Favg_ms_frame_delta", scd.frameToFrameTime.avgTime);
+ endEntry();
+ }
+ }
+
+ scd.beginToEndSamples.append(scd.beginToEndTimer.elapsed());
+ if (scd.beginToEndSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.beginToEndSamples,
+ &scd.beginToEndFrameTime.minTime, &scd.beginToEndFrameTime.maxTime, &scd.beginToEndFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::FrameBuildTime, ts.elapsed(), sc);
+ writeInt("frames_since_resize", frameCount);
+ writeInt("min_ms_frame_build", scd.beginToEndFrameTime.minTime);
+ writeInt("max_ms_frame_build", scd.beginToEndFrameTime.maxTime);
+ writeFloat("Favg_ms_frame_build", scd.beginToEndFrameTime.avgTime);
+ endEntry();
+ }
+ }
+}
+
+void QRhiProfilerPrivate::swapChainFrameGpuTime(QRhiSwapChain *sc, float gpuTime)
+{
+ Sc &scd(swapchains[sc]);
+ scd.gpuFrameSamples.append(gpuTime);
+ if (scd.gpuFrameSamples.count() >= frameTimingWriteInterval) {
+ calcTiming(&scd.gpuFrameSamples,
+ &scd.gpuFrameTime.minTime, &scd.gpuFrameTime.maxTime, &scd.gpuFrameTime.avgTime);
+ if (outputDevice) {
+ startEntry(QRhiProfiler::GpuFrameTime, ts.elapsed(), sc);
+ writeFloat("Fmin_ms_gpu_frame_time", scd.gpuFrameTime.minTime);
+ writeFloat("Fmax_ms_gpu_frame_time", scd.gpuFrameTime.maxTime);
+ writeFloat("Favg_ms_gpu_frame_time", scd.gpuFrameTime.avgTime);
+ endEntry();
+ }
+ }
+}
+
+void QRhiProfilerPrivate::newReadbackBuffer(quint64 id, QRhiResource *src, quint32 size)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::NewReadbackBuffer, ts.elapsed(), src);
+ writeInt("id", id);
+ writeInt("size", size);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::releaseReadbackBuffer(quint64 id)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::ReleaseReadbackBuffer, ts.elapsed(), nullptr);
+ writeInt("id", id);
+ endEntry();
+}
+
+void QRhiProfilerPrivate::vmemStat(int realAllocCount, int subAllocCount, quint32 totalSize, quint32 unusedSize)
+{
+ if (!outputDevice)
+ return;
+
+ startEntry(QRhiProfiler::GpuMemAllocStats, ts.elapsed(), nullptr);
+ writeInt("real_alloc_count", realAllocCount);
+ writeInt("sub_alloc_count", subAllocCount);
+ writeInt("total_size", totalSize);
+ writeInt("unused_size", unusedSize);
+ endEntry();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhiprofiler_p.h b/src/gui/rhi/qrhiprofiler_p.h
new file mode 100644
index 0000000000..89fd0a8798
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIPROFILER_H
+#define QRHIPROFILER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiProfilerPrivate;
+class QIODevice;
+
+class Q_GUI_EXPORT QRhiProfiler
+{
+public:
+ enum StreamOp {
+ NewBuffer = 1,
+ ReleaseBuffer,
+ NewBufferStagingArea,
+ ReleaseBufferStagingArea,
+ NewRenderBuffer,
+ ReleaseRenderBuffer,
+ NewTexture,
+ ReleaseTexture,
+ NewTextureStagingArea,
+ ReleaseTextureStagingArea,
+ ResizeSwapChain,
+ ReleaseSwapChain,
+ NewReadbackBuffer,
+ ReleaseReadbackBuffer,
+ GpuMemAllocStats,
+ GpuFrameTime,
+ FrameToFrameTime,
+ FrameBuildTime
+ };
+
+ ~QRhiProfiler();
+
+ void setDevice(QIODevice *device);
+
+ void addVMemAllocatorStats();
+
+ int frameTimingWriteInterval() const;
+ void setFrameTimingWriteInterval(int frameCount);
+
+ struct CpuTime {
+ qint64 minTime = 0;
+ qint64 maxTime = 0;
+ float avgTime = 0;
+ };
+
+ struct GpuTime {
+ float minTime = 0;
+ float maxTime = 0;
+ float avgTime = 0;
+ };
+
+ CpuTime frameToFrameTimes(QRhiSwapChain *sc) const;
+ CpuTime frameBuildTimes(QRhiSwapChain *sc) const; // beginFrame - endFrame
+ GpuTime gpuFrameTimes(QRhiSwapChain *sc) const;
+
+private:
+ Q_DISABLE_COPY(QRhiProfiler)
+ QRhiProfiler();
+ QRhiProfilerPrivate *d;
+ friend class QRhiImplementation;
+ friend class QRhiProfilerPrivate;
+};
+
+Q_DECLARE_TYPEINFO(QRhiProfiler::CpuTime, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiProfiler::GpuTime, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhiprofiler_p_p.h b/src/gui/rhi/qrhiprofiler_p_p.h
new file mode 100644
index 0000000000..49c6bd78ed
--- /dev/null
+++ b/src/gui/rhi/qrhiprofiler_p_p.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIPROFILER_P_H
+#define QRHIPROFILER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhiprofiler_p.h"
+#include <QElapsedTimer>
+#include <QHash>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiProfilerPrivate
+{
+public:
+ static QRhiProfilerPrivate *get(QRhiProfiler *p) { return p->d; }
+
+ void newBuffer(QRhiBuffer *buf, quint32 realSize, int backingGpuBufCount, int backingCpuBufCount);
+ void releaseBuffer(QRhiBuffer *buf);
+ void newBufferStagingArea(QRhiBuffer *buf, int slot, quint32 size);
+ void releaseBufferStagingArea(QRhiBuffer *buf, int slot);
+
+ void newRenderBuffer(QRhiRenderBuffer *rb, bool transientBacking, bool winSysBacking, int sampleCount);
+ void releaseRenderBuffer(QRhiRenderBuffer *rb);
+
+ void newTexture(QRhiTexture *tex, bool owns, int mipCount, int layerCount, int sampleCount);
+ void releaseTexture(QRhiTexture *tex);
+ void newTextureStagingArea(QRhiTexture *tex, int slot, quint32 size);
+ void releaseTextureStagingArea(QRhiTexture *tex, int slot);
+
+ void resizeSwapChain(QRhiSwapChain *sc, int bufferCount, int msaaBufferCount, int sampleCount);
+ void releaseSwapChain(QRhiSwapChain *sc);
+
+ void beginSwapChainFrame(QRhiSwapChain *sc);
+ void endSwapChainFrame(QRhiSwapChain *sc, int frameCount);
+ void swapChainFrameGpuTime(QRhiSwapChain *sc, float gpuTimeMs);
+
+ void newReadbackBuffer(quint64 id, QRhiResource *src, quint32 size);
+ void releaseReadbackBuffer(quint64 id);
+
+ void vmemStat(int realAllocCount, int subAllocCount, quint32 totalSize, quint32 unusedSize);
+
+ void startEntry(QRhiProfiler::StreamOp op, qint64 timestamp, QRhiResource *res);
+ void writeInt(const char *key, qint64 v);
+ void writeFloat(const char *key, float f);
+ void endEntry();
+
+ QRhiImplementation *rhiDWhenEnabled = nullptr;
+ QIODevice *outputDevice = nullptr;
+ QElapsedTimer ts;
+ QByteArray buf;
+ static const int DEFAULT_FRAME_TIMING_WRITE_INTERVAL = 120; // frames
+ int frameTimingWriteInterval = DEFAULT_FRAME_TIMING_WRITE_INTERVAL;
+ struct Sc {
+ Sc() {
+ frameToFrameSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ beginToEndSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ gpuFrameSamples.reserve(DEFAULT_FRAME_TIMING_WRITE_INTERVAL);
+ }
+ QElapsedTimer frameToFrameTimer;
+ bool frameToFrameRunning = false;
+ QElapsedTimer beginToEndTimer;
+ QVector<qint64> frameToFrameSamples;
+ QVector<qint64> beginToEndSamples;
+ QVector<float> gpuFrameSamples;
+ QRhiProfiler::CpuTime frameToFrameTime;
+ QRhiProfiler::CpuTime beginToEndFrameTime;
+ QRhiProfiler::GpuTime gpuFrameTime;
+ };
+ QHash<QRhiSwapChain *, Sc> swapchains;
+};
+
+Q_DECLARE_TYPEINFO(QRhiProfilerPrivate::Sc, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
new file mode 100644
index 0000000000..7c4eeaf226
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -0,0 +1,6071 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qrhivulkan_p_p.h"
+#include "qrhivulkanext_p.h"
+
+#define VMA_IMPLEMENTATION
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_RECORDING_ENABLED 0
+#define VMA_DEDICATED_ALLOCATION 0
+#ifdef QT_DEBUG
+#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1
+#endif
+#include "vk_mem_alloc.h"
+
+#include <qmath.h>
+#include <QVulkanFunctions>
+#include <QVulkanWindow>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the
+ rendering thread to vsync. Textures and "static" buffers are device local,
+ and a separate, host visible staging buffer is used to upload data to them.
+ "Dynamic" buffers are in host visible memory and are duplicated (since there
+ can be 2 frames in flight). This is handled transparently to the application.
+*/
+
+/*!
+ \class QRhiVulkanInitParams
+ \inmodule QtRhi
+ \brief Vulkan specific initialization parameters.
+
+ A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to
+ the user to ensure this is available and initialized. This is typically
+ done in main() similarly to the following:
+
+ \badcode
+ int main(int argc, char **argv)
+ {
+ ...
+
+ QVulkanInstance inst;
+ #ifndef Q_OS_ANDROID
+ inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
+ #else
+ inst.setLayers(QByteArrayList()
+ << "VK_LAYER_GOOGLE_threading"
+ << "VK_LAYER_LUNARG_parameter_validation"
+ << "VK_LAYER_LUNARG_object_tracker"
+ << "VK_LAYER_LUNARG_core_validation"
+ << "VK_LAYER_LUNARG_image"
+ << "VK_LAYER_LUNARG_swapchain"
+ << "VK_LAYER_GOOGLE_unique_objects");
+ #endif
+ inst.setExtensions(QByteArrayList()
+ << "VK_KHR_get_physical_device_properties2");
+ if (!inst.create())
+ qFatal("Vulkan not available");
+
+ ...
+ }
+ \endcode
+
+ The example here has two optional aspects: it enables the
+ \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan
+ validation layers}, when they are available, and also enables the
+ VK_KHR_get_physical_device_properties2 extension (part of Vulkan 1.1), when
+ available. The former is useful during the development phase (remember that
+ QVulkanInstance conveniently redirects messages and warnings to qDebug).
+ Avoid enabling it in production builds, however. The latter is important in
+ order to make QRhi::CustomInstanceStepRate available with Vulkan since
+ VK_EXT_vertex_attribute_divisor (part of Vulkan 1.1) depends on it. It can
+ be omitted when instanced drawing with a non-one step rate is not used.
+
+ Once this is done, a Vulkan-based QRhi can be created by passing the
+ instance and a QWindow with its surface type set to
+ QSurface::VulkanSurface:
+
+ \badcode
+ QRhiVulkanInitParams params;
+ params.inst = vulkanInstance;
+ params.window = window;
+ rhi = QRhi::create(QRhi::Vulkan, &params);
+ \endcode
+
+ The window is optional and can be omitted. This is not recommended however
+ because there is then no way to ensure presenting is supported while
+ choosing a graphics queue.
+
+ \note Even when a window is specified, QRhiSwapChain objects can be created
+ for other windows as well, as long as they all have their
+ QWindow::surfaceType() set to QSurface::VulkanSurface.
+
+ \section2 Working with existing Vulkan devices
+
+ When interoperating with another graphics engine, it may be necessary to
+ get a QRhi instance that uses the same Vulkan device. This can be achieved
+ by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create().
+
+ The physical device and device object must then be set to a non-null value.
+ In addition, either the graphics queue family index or the graphics queue
+ object itself is required. Prefer the former, whenever possible since
+ deducing the index is not possible afterwards. Optionally, an existing
+ command pool object can be specified as well, and, also optionally,
+ vmemAllocator can be used to share the same
+ \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan
+ memory allocator} between two QRhi instances.
+
+ The QRhi does not take ownership of any of the external objects.
+ */
+
+/*!
+ \class QRhiVulkanNativeHandles
+ \inmodule QtRhi
+ \brief Collects device, queue, and other Vulkan objects that are used by the QRhi.
+
+ \note Ownership of the Vulkan objects is never transferred.
+ */
+
+/*!
+ \class QRhiVulkanTextureNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Vulkan image object that is backing a QRhiTexture.
+
+ Importing and exporting Vulkan image objects that back a QRhiTexture when
+ running with the Vulkan backend is supported via this class. Ownership of
+ the Vulkan object is never transferred.
+
+ \note Memory allocation details are not exposed. This is intentional since
+ memory is typically suballocated from a bigger chunk of VkDeviceMemory, and
+ exposing the allocator details is not desirable for now.
+ */
+
+/*!
+ \class QRhiVulkanCommandBufferNativeHandles
+ \inmodule QtRhi
+ \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer.
+
+ \note The Vulkan command buffer object is only guaranteed to be valid, and
+ in recording state, while recording a frame. That is, between a
+ \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
+ \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+ \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair.
+ */
+
+static inline VkDeviceSize aligned(VkDeviceSize v, VkDeviceSize byteAlign)
+{
+ return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+static QVulkanInstance *globalVulkanInstance;
+
+static void VKAPI_PTR wrap_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties)
+{
+ globalVulkanInstance->functions()->vkGetPhysicalDeviceProperties(physicalDevice, pProperties);
+}
+
+static void VKAPI_PTR wrap_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties)
+{
+ globalVulkanInstance->functions()->vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
+}
+
+static VkResult VKAPI_PTR wrap_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
+}
+
+void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkFreeMemory(device, memory, pAllocator);
+}
+
+VkResult VKAPI_PTR wrap_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkMapMemory(device, memory, offset, size, flags, ppData);
+}
+
+void VKAPI_PTR wrap_vkUnmapMemory(VkDevice device, VkDeviceMemory memory)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory);
+}
+
+VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkFlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
+}
+
+VkResult VKAPI_PTR wrap_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkInvalidateMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges);
+}
+
+VkResult VKAPI_PTR wrap_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkBindBufferMemory(device, buffer, memory, memoryOffset);
+}
+
+VkResult VKAPI_PTR wrap_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkBindImageMemory(device, image, memory, memoryOffset);
+}
+
+void VKAPI_PTR wrap_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkGetBufferMemoryRequirements(device, buffer, pMemoryRequirements);
+}
+
+void VKAPI_PTR wrap_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkGetImageMemoryRequirements(device, image, pMemoryRequirements);
+}
+
+VkResult VKAPI_PTR wrap_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer);
+}
+
+void VKAPI_PTR wrap_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkDestroyBuffer(device, buffer, pAllocator);
+}
+
+VkResult VKAPI_PTR wrap_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage)
+{
+ return globalVulkanInstance->deviceFunctions(device)->vkCreateImage(device, pCreateInfo, pAllocator, pImage);
+}
+
+void VKAPI_PTR wrap_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator)
+{
+ globalVulkanInstance->deviceFunctions(device)->vkDestroyImage(device, image, pAllocator);
+}
+
+static inline VmaAllocation toVmaAllocation(QVkAlloc a)
+{
+ return reinterpret_cast<VmaAllocation>(a);
+}
+
+static inline VmaAllocator toVmaAllocator(QVkAllocator a)
+{
+ return reinterpret_cast<VmaAllocator>(a);
+}
+
+QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice)
+ : ofr(this)
+{
+ inst = params->inst;
+ maybeWindow = params->window; // may be null
+
+ importedDevice = importDevice != nullptr;
+ if (importedDevice) {
+ physDev = importDevice->physDev;
+ dev = importDevice->dev;
+ if (physDev && dev) {
+ gfxQueueFamilyIdx = importDevice->gfxQueueFamilyIdx;
+ gfxQueue = importDevice->gfxQueue;
+ if (importDevice->cmdPool) {
+ importedCmdPool = true;
+ cmdPool = importDevice->cmdPool;
+ }
+ if (importDevice->vmemAllocator) {
+ importedAllocator = true;
+ allocator = importDevice->vmemAllocator;
+ }
+ } else {
+ qWarning("No (physical) Vulkan device is given, cannot import");
+ importedDevice = false;
+ }
+ }
+}
+
+static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object,
+ size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage)
+{
+ Q_UNUSED(flags);
+ Q_UNUSED(objectType);
+ Q_UNUSED(object);
+ Q_UNUSED(location);
+ Q_UNUSED(messageCode);
+ Q_UNUSED(pLayerPrefix);
+
+ // Filter out certain misleading validation layer messages, as per
+ // VulkanMemoryAllocator documentation.
+ if (strstr(pMessage, "Mapping an image with layout")
+ && strstr(pMessage, "can result in undefined behavior if this memory is used by the device"))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool QRhiVulkan::create(QRhi::Flags flags)
+{
+ Q_UNUSED(flags);
+ Q_ASSERT(inst);
+
+ globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application
+
+ f = inst->functions();
+
+ QVector<VkQueueFamilyProperties> queueFamilyProps;
+ auto queryQueueFamilyProps = [this, &queueFamilyProps] {
+ uint32_t queueCount = 0;
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
+ queueFamilyProps.resize(queueCount);
+ f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data());
+ };
+
+ if (!importedDevice) {
+ uint32_t physDevCount = 0;
+ f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr);
+ if (!physDevCount) {
+ qWarning("No physical devices");
+ return false;
+ }
+ QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount);
+ VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data());
+ if (err != VK_SUCCESS || !physDevCount) {
+ qWarning("Failed to enumerate physical devices: %d", err);
+ return false;
+ }
+ int physDevIndex = -1;
+ int requestedPhysDevIndex = -1;
+ if (qEnvironmentVariableIsSet("QT_VK_PHYSICAL_DEVICE_INDEX"))
+ requestedPhysDevIndex = qEnvironmentVariableIntValue("QT_VK_PHYSICAL_DEVICE_INDEX");
+ for (uint32_t i = 0; i < physDevCount; ++i) {
+ f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties);
+ qDebug("Physical device %d: '%s' %d.%d.%d", i,
+ physDevProperties.deviceName,
+ VK_VERSION_MAJOR(physDevProperties.driverVersion),
+ VK_VERSION_MINOR(physDevProperties.driverVersion),
+ VK_VERSION_PATCH(physDevProperties.driverVersion));
+ if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) {
+ physDevIndex = i;
+ qDebug(" using this physical device");
+ }
+ }
+ if (physDevIndex < 0) {
+ qWarning("No matching physical device");
+ return false;
+ }
+ physDev = physDevs[physDevIndex];
+
+ queryQueueFamilyProps();
+
+ gfxQueue = VK_NULL_HANDLE;
+
+ // We only support combined graphics+present queues. When it comes to
+ // compute, only combined graphics+compute queue is used, compute gets
+ // disabled otherwise.
+ gfxQueueFamilyIdx = -1;
+ int computelessGfxQueueCandidateIdx = -1;
+ for (int i = 0; i < queueFamilyProps.count(); ++i) {
+ qDebug("queue family %d: flags=0x%x count=%d", i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount);
+ if (gfxQueueFamilyIdx == -1
+ && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+ && (!maybeWindow || inst->supportsPresent(physDev, i, maybeWindow)))
+ {
+ if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
+ gfxQueueFamilyIdx = i;
+ else if (computelessGfxQueueCandidateIdx == -1)
+ computelessGfxQueueCandidateIdx = i;
+ }
+ }
+ if (gfxQueueFamilyIdx == -1) {
+ if (computelessGfxQueueCandidateIdx != -1) {
+ gfxQueueFamilyIdx = computelessGfxQueueCandidateIdx;
+ } else {
+ qWarning("No graphics (or no graphics+present) queue family found");
+ return false;
+ }
+ }
+
+ VkDeviceQueueCreateInfo queueInfo[2];
+ const float prio[] = { 0 };
+ memset(queueInfo, 0, sizeof(queueInfo));
+ queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queueInfo[0].queueFamilyIndex = gfxQueueFamilyIdx;
+ queueInfo[0].queueCount = 1;
+ queueInfo[0].pQueuePriorities = prio;
+
+ QVector<const char *> devLayers;
+ if (inst->layers().contains("VK_LAYER_LUNARG_standard_validation"))
+ devLayers.append("VK_LAYER_LUNARG_standard_validation");
+
+ uint32_t devExtCount = 0;
+ f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr);
+ QVector<VkExtensionProperties> devExts(devExtCount);
+ f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, devExts.data());
+ qDebug("%d device extensions available", devExts.count());
+
+ QVector<const char *> requestedDevExts;
+ requestedDevExts.append("VK_KHR_swapchain");
+
+ debugMarkersAvailable = false;
+ vertexAttribDivisorAvailable = false;
+ for (const VkExtensionProperties &ext : devExts) {
+ if (!strcmp(ext.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
+ debugMarkersAvailable = true;
+ } else if (!strcmp(ext.extensionName, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
+ if (inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2"))) {
+ requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
+ vertexAttribDivisorAvailable = true;
+ }
+ }
+ }
+
+ VkDeviceCreateInfo devInfo;
+ memset(&devInfo, 0, sizeof(devInfo));
+ devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ devInfo.queueCreateInfoCount = 1;
+ devInfo.pQueueCreateInfos = queueInfo;
+ devInfo.enabledLayerCount = devLayers.count();
+ devInfo.ppEnabledLayerNames = devLayers.constData();
+ devInfo.enabledExtensionCount = requestedDevExts.count();
+ devInfo.ppEnabledExtensionNames = requestedDevExts.constData();
+
+ err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create device: %d", err);
+ return false;
+ }
+ }
+
+ df = inst->deviceFunctions(dev);
+
+ if (!importedCmdPool) {
+ VkCommandPoolCreateInfo poolInfo;
+ memset(&poolInfo, 0, sizeof(poolInfo));
+ poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ poolInfo.queueFamilyIndex = gfxQueueFamilyIdx;
+ VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command pool: %d", err);
+ return false;
+ }
+ }
+
+ if (gfxQueueFamilyIdx != -1) {
+ if (!gfxQueue)
+ df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, 0, &gfxQueue);
+
+ if (queueFamilyProps.isEmpty())
+ queryQueueFamilyProps();
+
+ hasCompute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0;
+ timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits;
+ }
+
+ f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties);
+ ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment;
+ // helps little with an optimal offset of 1 (on some drivers) when the spec
+ // elsewhere states that the minimum bufferOffset is 4...
+ texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment);
+
+ f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures);
+ hasWideLines = physDevFeatures.wideLines;
+
+ if (!importedAllocator) {
+ VmaVulkanFunctions afuncs;
+ afuncs.vkGetPhysicalDeviceProperties = wrap_vkGetPhysicalDeviceProperties;
+ afuncs.vkGetPhysicalDeviceMemoryProperties = wrap_vkGetPhysicalDeviceMemoryProperties;
+ afuncs.vkAllocateMemory = wrap_vkAllocateMemory;
+ afuncs.vkFreeMemory = wrap_vkFreeMemory;
+ afuncs.vkMapMemory = wrap_vkMapMemory;
+ afuncs.vkUnmapMemory = wrap_vkUnmapMemory;
+ afuncs.vkFlushMappedMemoryRanges = wrap_vkFlushMappedMemoryRanges;
+ afuncs.vkInvalidateMappedMemoryRanges = wrap_vkInvalidateMappedMemoryRanges;
+ afuncs.vkBindBufferMemory = wrap_vkBindBufferMemory;
+ afuncs.vkBindImageMemory = wrap_vkBindImageMemory;
+ afuncs.vkGetBufferMemoryRequirements = wrap_vkGetBufferMemoryRequirements;
+ afuncs.vkGetImageMemoryRequirements = wrap_vkGetImageMemoryRequirements;
+ afuncs.vkCreateBuffer = wrap_vkCreateBuffer;
+ afuncs.vkDestroyBuffer = wrap_vkDestroyBuffer;
+ afuncs.vkCreateImage = wrap_vkCreateImage;
+ afuncs.vkDestroyImage = wrap_vkDestroyImage;
+
+ VmaAllocatorCreateInfo allocatorInfo;
+ memset(&allocatorInfo, 0, sizeof(allocatorInfo));
+ allocatorInfo.physicalDevice = physDev;
+ allocatorInfo.device = dev;
+ allocatorInfo.pVulkanFunctions = &afuncs;
+ VmaAllocator vmaallocator;
+ VkResult err = vmaCreateAllocator(&allocatorInfo, &vmaallocator);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create allocator: %d", err);
+ return false;
+ }
+ allocator = vmaallocator;
+ }
+
+ inst->installDebugOutputFilter(qvk_debug_filter);
+
+ VkDescriptorPool pool;
+ VkResult err = createDescriptorPool(&pool);
+ if (err == VK_SUCCESS)
+ descriptorPools.append(pool);
+ else
+ qWarning("Failed to create initial descriptor pool: %d", err);
+
+ VkQueryPoolCreateInfo timestampQueryPoolInfo;
+ memset(&timestampQueryPoolInfo, 0, sizeof(timestampQueryPoolInfo));
+ timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+ timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
+ timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2;
+ err = df->vkCreateQueryPool(dev, &timestampQueryPoolInfo, nullptr, &timestampQueryPool);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create timestamp query pool: %d", err);
+ return false;
+ }
+ timestampQueryPoolMap.resize(QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair
+ timestampQueryPoolMap.fill(false);
+
+ if (debugMarkersAvailable) {
+ vkCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerBeginEXT"));
+ vkCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerEndEXT"));
+ vkCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerInsertEXT"));
+ vkDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkDebugMarkerSetObjectNameEXT"));
+ }
+
+ nativeHandlesStruct.physDev = physDev;
+ nativeHandlesStruct.dev = dev;
+ nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx;
+ nativeHandlesStruct.gfxQueue = gfxQueue;
+ nativeHandlesStruct.cmdPool = cmdPool;
+ nativeHandlesStruct.vmemAllocator = allocator;
+
+ return true;
+}
+
+void QRhiVulkan::destroy()
+{
+ if (!df)
+ return;
+
+ df->vkDeviceWaitIdle(dev);
+
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ if (ofr.cmdFence) {
+ df->vkDestroyFence(dev, ofr.cmdFence, nullptr);
+ ofr.cmdFence = VK_NULL_HANDLE;
+ }
+
+ if (ofr.cbWrapper.cb) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, &ofr.cbWrapper.cb);
+ ofr.cbWrapper.cb = VK_NULL_HANDLE;
+ }
+
+ if (pipelineCache) {
+ df->vkDestroyPipelineCache(dev, pipelineCache, nullptr);
+ pipelineCache = VK_NULL_HANDLE;
+ }
+
+ for (const DescriptorPoolData &pool : descriptorPools)
+ df->vkDestroyDescriptorPool(dev, pool.pool, nullptr);
+
+ descriptorPools.clear();
+
+ if (timestampQueryPool) {
+ df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr);
+ timestampQueryPool = VK_NULL_HANDLE;
+ }
+
+ if (!importedAllocator && allocator) {
+ vmaDestroyAllocator(toVmaAllocator(allocator));
+ allocator = nullptr;
+ }
+
+ if (!importedCmdPool && cmdPool) {
+ df->vkDestroyCommandPool(dev, cmdPool, nullptr);
+ cmdPool = VK_NULL_HANDLE;
+ }
+
+ if (!importedDevice && dev) {
+ df->vkDestroyDevice(dev, nullptr);
+ inst->resetDeviceFunctions(dev);
+ dev = VK_NULL_HANDLE;
+ }
+
+ f = nullptr;
+ df = nullptr;
+}
+
+VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool)
+{
+ VkDescriptorPoolSize descPoolSizes[] = {
+ { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, QVK_UNIFORM_BUFFERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, QVK_UNIFORM_BUFFERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, QVK_STORAGE_BUFFERS_PER_POOL },
+ { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, QVK_STORAGE_IMAGES_PER_POOL }
+ };
+ VkDescriptorPoolCreateInfo descPoolInfo;
+ memset(&descPoolInfo, 0, sizeof(descPoolInfo));
+ descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ // Do not enable vkFreeDescriptorSets - sets are never freed on their own
+ // (good so no trouble with fragmentation), they just deref their pool
+ // which is then reset at some point (or not).
+ descPoolInfo.flags = 0;
+ descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL;
+ descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]);
+ descPoolInfo.pPoolSizes = descPoolSizes;
+ return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool);
+}
+
+bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex)
+{
+ auto tryAllocate = [this, allocInfo, result](int poolIndex) {
+ allocInfo->descriptorPool = descriptorPools[poolIndex].pool;
+ VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result);
+ if (r == VK_SUCCESS)
+ descriptorPools[poolIndex].refCount += 1;
+ return r;
+ };
+
+ int lastPoolIdx = descriptorPools.count() - 1;
+ for (int i = lastPoolIdx; i >= 0; --i) {
+ if (descriptorPools[i].refCount == 0) {
+ df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0);
+ descriptorPools[i].allocedDescSets = 0;
+ }
+ if (descriptorPools[i].allocedDescSets + allocInfo->descriptorSetCount <= QVK_DESC_SETS_PER_POOL) {
+ VkResult err = tryAllocate(i);
+ if (err == VK_SUCCESS) {
+ descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount;
+ *resultPoolIndex = i;
+ return true;
+ }
+ }
+ }
+
+ VkDescriptorPool newPool;
+ VkResult poolErr = createDescriptorPool(&newPool);
+ if (poolErr == VK_SUCCESS) {
+ descriptorPools.append(newPool);
+ lastPoolIdx = descriptorPools.count() - 1;
+ VkResult err = tryAllocate(lastPoolIdx);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to allocate descriptor set from new pool too, giving up: %d", err);
+ return false;
+ }
+ descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount;
+ *resultPoolIndex = lastPoolIdx;
+ return true;
+ } else {
+ qWarning("Failed to allocate new descriptor pool: %d", poolErr);
+ return false;
+ }
+}
+
+static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+ const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+ switch (format) {
+ case QRhiTexture::RGBA8:
+ return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM;
+ case QRhiTexture::BGRA8:
+ return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
+ case QRhiTexture::R8:
+ return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM;
+ case QRhiTexture::R16:
+ return VK_FORMAT_R16_UNORM;
+ case QRhiTexture::RED_OR_ALPHA8:
+ return VK_FORMAT_R8_UNORM;
+
+ case QRhiTexture::RGBA16F:
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case QRhiTexture::RGBA32F:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+
+ case QRhiTexture::D16:
+ return VK_FORMAT_D16_UNORM;
+ case QRhiTexture::D32F:
+ return VK_FORMAT_D32_SFLOAT;
+
+ case QRhiTexture::BC1:
+ return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK;
+ case QRhiTexture::BC2:
+ return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK;
+ case QRhiTexture::BC3:
+ return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK;
+ case QRhiTexture::BC4:
+ return VK_FORMAT_BC4_UNORM_BLOCK;
+ case QRhiTexture::BC5:
+ return VK_FORMAT_BC5_UNORM_BLOCK;
+ case QRhiTexture::BC6H:
+ return VK_FORMAT_BC6H_UFLOAT_BLOCK;
+ case QRhiTexture::BC7:
+ return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK;
+
+ case QRhiTexture::ETC2_RGB8:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
+ case QRhiTexture::ETC2_RGB8A1:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
+ case QRhiTexture::ETC2_RGBA8:
+ return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
+
+ case QRhiTexture::ASTC_4x4:
+ return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
+ case QRhiTexture::ASTC_5x4:
+ return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
+ case QRhiTexture::ASTC_5x5:
+ return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_6x5:
+ return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_6x6:
+ return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x5:
+ return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x6:
+ return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_8x8:
+ return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x5:
+ return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x6:
+ return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x8:
+ return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
+ case QRhiTexture::ASTC_10x10:
+ return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
+ case QRhiTexture::ASTC_12x10:
+ return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
+ case QRhiTexture::ASTC_12x12:
+ return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
+
+ default:
+ Q_UNREACHABLE();
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ }
+}
+
+static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags)
+{
+ switch (format) {
+ case VK_FORMAT_R8G8B8A8_UNORM:
+ return QRhiTexture::RGBA8;
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::RGBA8;
+ case VK_FORMAT_B8G8R8A8_UNORM:
+ return QRhiTexture::BGRA8;
+ case VK_FORMAT_B8G8R8A8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::BGRA8;
+ case VK_FORMAT_R8_UNORM:
+ return QRhiTexture::R8;
+ case VK_FORMAT_R8_SRGB:
+ if (flags)
+ (*flags) |= QRhiTexture::sRGB;
+ return QRhiTexture::R8;
+ case VK_FORMAT_R16_UNORM:
+ return QRhiTexture::R16;
+ default: // this cannot assert, must warn and return unknown
+ qWarning("VkFormat %d is not a recognized uncompressed color format", format);
+ break;
+ }
+ return QRhiTexture::UnknownFormat;
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+ switch (format) {
+ case QRhiTexture::Format::D16:
+ Q_FALLTHROUGH();
+ case QRhiTexture::Format::D32F:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+// Transient images ("render buffers") backed by lazily allocated memory are
+// managed manually without going through vk_mem_alloc since it does not offer
+// any support for such images. This should be ok since in practice there
+// should be very few of such images.
+
+uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex)
+{
+ VkPhysicalDeviceMemoryProperties physDevMemProps;
+ f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps);
+
+ VkMemoryRequirements memReq;
+ df->vkGetImageMemoryRequirements(dev, img, &memReq);
+ uint32_t memTypeIndex = uint32_t(-1);
+
+ if (memReq.memoryTypeBits) {
+ // Find a device local + lazily allocated, or at least device local memtype.
+ const VkMemoryType *memType = physDevMemProps.memoryTypes;
+ bool foundDevLocal = false;
+ for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) {
+ if (memReq.memoryTypeBits & (1 << i)) {
+ if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
+ if (!foundDevLocal) {
+ foundDevLocal = true;
+ memTypeIndex = i;
+ }
+ if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
+ memTypeIndex = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return memTypeIndex;
+}
+
+bool QRhiVulkan::createTransientImage(VkFormat format,
+ const QSize &pixelSize,
+ VkImageUsageFlags usage,
+ VkImageAspectFlags aspectMask,
+ VkSampleCountFlagBits samples,
+ VkDeviceMemory *mem,
+ VkImage *images,
+ VkImageView *views,
+ int count)
+{
+ VkMemoryRequirements memReq;
+ VkResult err;
+
+ for (int i = 0; i < count; ++i) {
+ VkImageCreateInfo imgInfo;
+ memset(&imgInfo, 0, sizeof(imgInfo));
+ imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imgInfo.imageType = VK_IMAGE_TYPE_2D;
+ imgInfo.format = format;
+ imgInfo.extent.width = pixelSize.width();
+ imgInfo.extent.height = pixelSize.height();
+ imgInfo.extent.depth = 1;
+ imgInfo.mipLevels = imgInfo.arrayLayers = 1;
+ imgInfo.samples = samples;
+ imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
+ imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+ err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image: %d", err);
+ return false;
+ }
+
+ // Assume the reqs are the same since the images are same in every way.
+ // Still, call GetImageMemReq for every image, in order to prevent the
+ // validation layer from complaining.
+ df->vkGetImageMemoryRequirements(dev, images[i], &memReq);
+ }
+
+ VkMemoryAllocateInfo memInfo;
+ memset(&memInfo, 0, sizeof(memInfo));
+ memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ memInfo.allocationSize = aligned(memReq.size, memReq.alignment) * count;
+
+ uint32_t startIndex = 0;
+ do {
+ memInfo.memoryTypeIndex = chooseTransientImageMemType(images[0], startIndex);
+ if (memInfo.memoryTypeIndex == uint32_t(-1)) {
+ qWarning("No suitable memory type found");
+ return false;
+ }
+ startIndex = memInfo.memoryTypeIndex + 1;
+ err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem);
+ if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) {
+ qWarning("Failed to allocate image memory: %d", err);
+ return false;
+ }
+ } while (err != VK_SUCCESS);
+
+ VkDeviceSize ofs = 0;
+ for (int i = 0; i < count; ++i) {
+ err = df->vkBindImageMemory(dev, images[i], *mem, ofs);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to bind image memory: %d", err);
+ return false;
+ }
+ ofs += aligned(memReq.size, memReq.alignment);
+
+ VkImageViewCreateInfo imgViewInfo;
+ memset(&imgViewInfo, 0, sizeof(imgViewInfo));
+ imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ imgViewInfo.image = images[i];
+ imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ imgViewInfo.format = format;
+ imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ imgViewInfo.subresourceRange.aspectMask = aspectMask;
+ imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
+
+ err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image view: %d", err);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+VkFormat QRhiVulkan::optimalDepthStencilFormat()
+{
+ if (optimalDsFormat != VK_FORMAT_UNDEFINED)
+ return optimalDsFormat;
+
+ const VkFormat dsFormatCandidates[] = {
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D16_UNORM_S8_UINT
+ };
+ const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat);
+ int dsFormatIdx = 0;
+ while (dsFormatIdx < dsFormatCandidateCount) {
+ optimalDsFormat = dsFormatCandidates[dsFormatIdx];
+ VkFormatProperties fmtProp;
+ f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp);
+ if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
+ break;
+ ++dsFormatIdx;
+ }
+ if (dsFormatIdx == dsFormatCandidateCount)
+ qWarning("Failed to find an optimal depth-stencil format");
+
+ return optimalDsFormat;
+}
+
+bool QRhiVulkan::createDefaultRenderPass(VkRenderPass *rp, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat)
+{
+ VkAttachmentDescription attDesc[3];
+ memset(attDesc, 0, sizeof(attDesc));
+
+ // attachment list layout is color (1), ds (0-1), resolve (0-1)
+
+ attDesc[0].format = colorFormat;
+ attDesc[0].samples = samples;
+ attDesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[0].storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[0].finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ // clear on load + no store + lazy alloc + transient image should play
+ // nicely with tiled GPUs (no physical backing necessary for ds buffer)
+ attDesc[1].format = optimalDepthStencilFormat();
+ attDesc[1].samples = samples;
+ attDesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ attDesc[2].format = colorFormat;
+ attDesc[2].samples = VK_SAMPLE_COUNT_1_BIT;
+ attDesc[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ }
+
+ VkAttachmentReference colorRef = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ VkAttachmentReference dsRef = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+ VkAttachmentReference resolveRef = { 2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+
+ VkSubpassDescription subpassDesc;
+ memset(&subpassDesc, 0, sizeof(subpassDesc));
+ subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpassDesc.colorAttachmentCount = 1;
+ subpassDesc.pColorAttachments = &colorRef;
+ subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
+
+ // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own.
+ VkSubpassDependency subpassDep;
+ memset(&subpassDep, 0, sizeof(subpassDep));
+ subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL;
+ subpassDep.dstSubpass = 0;
+ subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ subpassDep.srcAccessMask = 0;
+ subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+ VkRenderPassCreateInfo rpInfo;
+ memset(&rpInfo, 0, sizeof(rpInfo));
+ rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rpInfo.attachmentCount = 1;
+ rpInfo.pAttachments = attDesc;
+ rpInfo.subpassCount = 1;
+ rpInfo.pSubpasses = &subpassDesc;
+ rpInfo.dependencyCount = 1;
+ rpInfo.pDependencies = &subpassDep;
+
+ if (hasDepthStencil)
+ rpInfo.attachmentCount += 1;
+
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ rpInfo.attachmentCount += 1;
+ subpassDesc.pResolveAttachments = &resolveRef;
+ }
+
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiVulkan::createOffscreenRenderPass(VkRenderPass *rp,
+ const QVector<QRhiColorAttachment> &colorAttachments,
+ bool preserveColor,
+ bool preserveDs,
+ QRhiRenderBuffer *depthStencilBuffer,
+ QRhiTexture *depthTexture)
+{
+ QVarLengthArray<VkAttachmentDescription, 8> attDescs;
+ QVarLengthArray<VkAttachmentReference, 8> colorRefs;
+ QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
+ const int colorAttCount = colorAttachments.count();
+
+ // attachment list layout is color (0-8), ds (0-1), resolve (0-8)
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachments[i].texture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat;
+ const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples;
+
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = vkformat;
+ attDesc.samples = samples;
+ attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attDesc.storeOp = colorAttachments[i].resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT
+ attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+
+ const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ colorRefs.append(ref);
+ }
+
+ const bool hasDepthStencil = depthStencilBuffer || depthTexture;
+ if (hasDepthStencil) {
+ const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat
+ : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat;
+ const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples
+ : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples;
+ const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
+ const VkAttachmentStoreOp storeOp = depthTexture ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = dsFormat;
+ attDesc.samples = samples;
+ attDesc.loadOp = loadOp;
+ attDesc.storeOp = storeOp;
+ attDesc.stencilLoadOp = loadOp;
+ attDesc.stencilStoreOp = storeOp;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+ }
+ VkAttachmentReference dsRef = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
+ for (int i = 0; i < colorAttCount; ++i) {
+ if (colorAttachments[i].resolveTexture()) {
+ QVkTexture *rtexD = QRHI_RES(QVkTexture, colorAttachments[i].resolveTexture());
+ if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
+ qWarning("Resolving into a multisample texture is not supported");
+
+ VkAttachmentDescription attDesc;
+ memset(&attDesc, 0, sizeof(attDesc));
+ attDesc.format = rtexD->vkformat;
+ attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+ attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
+ attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ attDescs.append(attDesc);
+
+ const VkAttachmentReference ref = { uint32_t(attDescs.count() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ resolveRefs.append(ref);
+ } else {
+ const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
+ resolveRefs.append(ref);
+ }
+ }
+
+ VkSubpassDescription subpassDesc;
+ memset(&subpassDesc, 0, sizeof(subpassDesc));
+ subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpassDesc.colorAttachmentCount = colorRefs.count();
+ Q_ASSERT(colorRefs.count() == resolveRefs.count());
+ subpassDesc.pColorAttachments = !colorRefs.isEmpty() ? colorRefs.constData() : nullptr;
+ subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &dsRef : nullptr;
+ subpassDesc.pResolveAttachments = !resolveRefs.isEmpty() ? resolveRefs.constData() : nullptr;
+
+ VkRenderPassCreateInfo rpInfo;
+ memset(&rpInfo, 0, sizeof(rpInfo));
+ rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rpInfo.attachmentCount = attDescs.count();
+ rpInfo.pAttachments = attDescs.constData();
+ rpInfo.subpassCount = 1;
+ rpInfo.pSubpasses = &subpassDesc;
+ // don't yet know the correct initial/final access and stage stuff for the
+ // implicit deps at this point, so leave it to the resource tracking to
+ // generate barriers
+
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
+
+ return true;
+}
+
+bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
+{
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ if (swapChainD->pixelSize.isEmpty()) {
+ qWarning("Surface size is 0, cannot create swapchain");
+ return false;
+ }
+
+ df->vkDeviceWaitIdle(dev);
+
+ if (!vkCreateSwapchainKHR) {
+ vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR"));
+ vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR"));
+ vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR"));
+ vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR"));
+ vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR"));
+ if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) {
+ qWarning("Swapchain functions not available");
+ return false;
+ }
+ }
+
+ VkSurfaceCapabilitiesKHR surfaceCaps;
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps);
+ quint32 reqBufferCount;
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::MinimalBufferCount)) {
+ reqBufferCount = qMax<quint32>(2, surfaceCaps.minImageCount);
+ } else {
+ const quint32 maxBuffers = QVkSwapChain::MAX_BUFFER_COUNT;
+ if (surfaceCaps.maxImageCount)
+ reqBufferCount = qMax(qMin(surfaceCaps.maxImageCount, maxBuffers), surfaceCaps.minImageCount);
+ else
+ reqBufferCount = qMax<quint32>(2, surfaceCaps.minImageCount);
+ }
+
+ VkSurfaceTransformFlagBitsKHR preTransform =
+ (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
+ ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
+ : surfaceCaps.currentTransform;
+
+ VkCompositeAlphaFlagBitsKHR compositeAlpha =
+ (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
+ ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
+ : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasPreMulAlpha)
+ && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR))
+ {
+ compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
+ }
+
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::SurfaceHasNonPreMulAlpha)
+ && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR))
+ {
+ compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
+ }
+
+ VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
+ if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
+ usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+
+ VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
+ if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) {
+ if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR))
+ presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+ else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
+ presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
+ }
+
+ // If the surface is different than before, then passing in the old
+ // swapchain associated with the old surface can fail the swapchain
+ // creation. (for example, Android loses the surface when backgrounding and
+ // restoring applications, and it also enforces failing swapchain creation
+ // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided)
+ const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface;
+
+ qDebug("Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d",
+ reuseExisting ? "recycled" : "new",
+ reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode);
+
+ VkSwapchainCreateInfoKHR swapChainInfo;
+ memset(&swapChainInfo, 0, sizeof(swapChainInfo));
+ swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ swapChainInfo.surface = swapChainD->surface;
+ swapChainInfo.minImageCount = reqBufferCount;
+ swapChainInfo.imageFormat = swapChainD->colorFormat;
+ swapChainInfo.imageColorSpace = swapChainD->colorSpace;
+ swapChainInfo.imageExtent = VkExtent2D { uint32_t(swapChainD->pixelSize.width()), uint32_t(swapChainD->pixelSize.height()) };
+ swapChainInfo.imageArrayLayers = 1;
+ swapChainInfo.imageUsage = usage;
+ swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ swapChainInfo.preTransform = preTransform;
+ swapChainInfo.compositeAlpha = compositeAlpha;
+ swapChainInfo.presentMode = presentMode;
+ swapChainInfo.clipped = true;
+ swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE;
+
+ VkSwapchainKHR newSwapChain;
+ VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create swapchain: %d", err);
+ return false;
+ }
+
+ if (swapChainD->sc)
+ releaseSwapChainResources(swapChain);
+
+ swapChainD->sc = newSwapChain;
+ swapChainD->lastConnectedSurface = swapChainD->surface;
+
+ quint32 actualSwapChainBufferCount = 0;
+ err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr);
+ if (err != VK_SUCCESS || actualSwapChainBufferCount < 2) {
+ qWarning("Failed to get swapchain images: %d (count=%u)", err, actualSwapChainBufferCount);
+ return false;
+ }
+
+ if (actualSwapChainBufferCount > QVkSwapChain::MAX_BUFFER_COUNT) {
+ qWarning("Too many swapchain buffers (%u)", actualSwapChainBufferCount);
+ return false;
+ }
+ if (actualSwapChainBufferCount != reqBufferCount)
+ qDebug("Actual swapchain buffer count is %u", actualSwapChainBufferCount);
+ swapChainD->bufferCount = actualSwapChainBufferCount;
+
+ VkImage swapChainImages[QVkSwapChain::MAX_BUFFER_COUNT];
+ err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to get swapchain images: %d", err);
+ return false;
+ }
+
+ VkImage msaaImages[QVkSwapChain::MAX_BUFFER_COUNT];
+ VkImageView msaaViews[QVkSwapChain::MAX_BUFFER_COUNT];
+ if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ if (!createTransientImage(swapChainD->colorFormat,
+ swapChainD->pixelSize,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ VK_IMAGE_ASPECT_COLOR_BIT,
+ swapChainD->samples,
+ &swapChainD->msaaImageMem,
+ msaaImages,
+ msaaViews,
+ swapChainD->bufferCount))
+ {
+ qWarning("Failed to create transient image for MSAA color buffer");
+ return false;
+ }
+ }
+
+ VkFenceCreateInfo fenceInfo;
+ memset(&fenceInfo, 0, sizeof(fenceInfo));
+ fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+ for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
+ image.image = swapChainImages[i];
+ if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ image.msaaImage = msaaImages[i];
+ image.msaaImageView = msaaViews[i];
+ }
+
+ VkImageViewCreateInfo imgViewInfo;
+ memset(&imgViewInfo, 0, sizeof(imgViewInfo));
+ imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ imgViewInfo.image = swapChainImages[i];
+ imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ imgViewInfo.format = swapChainD->colorFormat;
+ imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1;
+ err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create swapchain image view %d: %d", i, err);
+ return false;
+ }
+
+ image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone;
+ }
+
+ swapChainD->currentImageIndex = 0;
+
+ VkSemaphoreCreateInfo semInfo;
+ memset(&semInfo, 0, sizeof(semInfo));
+ semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
+
+ frame.imageAcquired = false;
+ frame.imageSemWaitable = false;
+
+ df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.imageFence);
+ frame.imageFenceWaitable = true; // fence was created in signaled state
+
+ df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem);
+ df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem);
+
+ err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command buffer fence: %d", err);
+ return false;
+ }
+ frame.cmdFenceWaitable = true; // fence was created in signaled state
+ }
+
+ swapChainD->currentFrameSlot = 0;
+
+ return true;
+}
+
+void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
+{
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+
+ if (swapChainD->sc == VK_NULL_HANDLE)
+ return;
+
+ df->vkDeviceWaitIdle(dev);
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]);
+ if (frame.cmdFence) {
+ if (frame.cmdFenceWaitable)
+ df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkDestroyFence(dev, frame.cmdFence, nullptr);
+ frame.cmdFence = VK_NULL_HANDLE;
+ frame.cmdFenceWaitable = false;
+ }
+ if (frame.imageFence) {
+ if (frame.imageFenceWaitable)
+ df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
+ df->vkDestroyFence(dev, frame.imageFence, nullptr);
+ frame.imageFence = VK_NULL_HANDLE;
+ frame.imageFenceWaitable = false;
+ }
+ if (frame.imageSem) {
+ df->vkDestroySemaphore(dev, frame.imageSem, nullptr);
+ frame.imageSem = VK_NULL_HANDLE;
+ }
+ if (frame.drawSem) {
+ df->vkDestroySemaphore(dev, frame.drawSem, nullptr);
+ frame.drawSem = VK_NULL_HANDLE;
+ }
+ if (frame.cmdBuf) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf);
+ frame.cmdBuf = VK_NULL_HANDLE;
+ }
+ }
+
+ for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
+ if (image.fb) {
+ df->vkDestroyFramebuffer(dev, image.fb, nullptr);
+ image.fb = VK_NULL_HANDLE;
+ }
+ if (image.imageView) {
+ df->vkDestroyImageView(dev, image.imageView, nullptr);
+ image.imageView = VK_NULL_HANDLE;
+ }
+ if (image.msaaImageView) {
+ df->vkDestroyImageView(dev, image.msaaImageView, nullptr);
+ image.msaaImageView = VK_NULL_HANDLE;
+ }
+ if (image.msaaImage) {
+ df->vkDestroyImage(dev, image.msaaImage, nullptr);
+ image.msaaImage = VK_NULL_HANDLE;
+ }
+ }
+
+ if (swapChainD->msaaImageMem) {
+ df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr);
+ swapChainD->msaaImageMem = VK_NULL_HANDLE;
+ }
+
+ vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr);
+ swapChainD->sc = VK_NULL_HANDLE;
+
+ // NB! surface and similar must remain intact
+}
+
+static inline bool checkDeviceLost(VkResult err)
+{
+ if (err == VK_ERROR_DEVICE_LOST) {
+ qWarning("Device lost");
+ return true;
+ }
+ return false;
+}
+
+QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+ Q_UNUSED(flags);
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ if (!frame.imageAcquired) {
+ // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate
+ // (note that we are using FIFO mode -> vsync)
+ if (frame.imageFenceWaitable) {
+ df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &frame.imageFence);
+ frame.imageFenceWaitable = false;
+ }
+
+ // move on to next swapchain image
+ VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX,
+ frame.imageSem, frame.imageFence, &frame.imageIndex);
+ if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) {
+ swapChainD->currentImageIndex = frame.imageIndex;
+ frame.imageSemWaitable = true;
+ frame.imageAcquired = true;
+ frame.imageFenceWaitable = true;
+ } else if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ return QRhi::FrameOpSwapChainOutOfDate;
+ } else {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to acquire next swapchain image: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ // Make sure the previous commands for the same image have finished. (note
+ // that this is based on the fence from the command buffer submit, nothing
+ // to do with the Present)
+ //
+ // Do this also for any other swapchain's commands with the same frame slot
+ // While this reduces concurrency, it keeps resource usage safe: swapchain
+ // A starting its frame 0, followed by swapchain B starting its own frame 0
+ // will make B wait for A's frame 0 commands, so if a resource is written
+ // in B's frame or when B checks for pending resource releases, that won't
+ // mess up A's in-flight commands (as they are not in flight anymore).
+ waitCommandCompletion(swapChainD->currentFrameSlot);
+
+ // Now is the time to read the timestamps for the previous frame for this slot.
+ if (frame.timestampQueryIndex >= 0) {
+ quint64 timestamp[2] = { 0, 0 };
+ VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, frame.timestampQueryIndex, 2,
+ 2 * sizeof(quint64), timestamp, sizeof(quint64), VK_QUERY_RESULT_64_BIT);
+ timestampQueryPoolMap.clearBit(frame.timestampQueryIndex / 2);
+ frame.timestampQueryIndex = -1;
+ if (err == VK_SUCCESS) {
+ quint64 mask = 0;
+ for (quint64 i = 0; i < timestampValidBits; i += 8)
+ mask |= 0xFFULL << i;
+ const quint64 ts0 = timestamp[0] & mask;
+ const quint64 ts1 = timestamp[1] & mask;
+ const float nsecsPerTick = physDevProperties.limits.timestampPeriod;
+ if (!qFuzzyIsNull(nsecsPerTick)) {
+ const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f;
+ // now we have the gpu time for the previous frame for this slot, report it
+ // (does not matter that it is not for this frame)
+ QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs));
+ }
+ } else {
+ qWarning("Failed to query timestamp: %d", err);
+ }
+ }
+
+ // build new draw command buffer
+ QRhi::FrameOpResult cbres = startCommandBuffer(&frame.cmdBuf);
+ if (cbres != QRhi::FrameOpSuccess)
+ return cbres;
+
+ // when profiling is enabled, pick a free query (pair) from the pool
+ int timestampQueryIdx = -1;
+ if (profilerPrivateOrNull()) {
+ for (int i = 0; i < timestampQueryPoolMap.count(); ++i) {
+ if (!timestampQueryPoolMap.testBit(i)) {
+ timestampQueryPoolMap.setBit(i);
+ timestampQueryIdx = i * 2;
+ break;
+ }
+ }
+ }
+ if (timestampQueryIdx >= 0) {
+ df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, timestampQueryIdx, 2);
+ // record timestamp at the start of the command buffer
+ df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
+ timestampQueryPool, timestampQueryIdx);
+ frame.timestampQueryIndex = timestampQueryIdx;
+ }
+
+ swapChainD->cbWrapper.cb = frame.cmdBuf;
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
+ swapChainD->rtWrapper.d.fb = image.fb;
+
+ currentFrameSlot = swapChainD->currentFrameSlot;
+ currentSwapChain = swapChainD;
+ if (swapChainD->ds)
+ swapChainD->ds->lastActiveFrameSlot = currentFrameSlot;
+
+ QRHI_PROF_F(beginSwapChainFrame(swapChain));
+
+ prepareNewFrame(&swapChainD->cbWrapper);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+ QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain);
+ Q_ASSERT(currentSwapChain == swapChainD);
+
+ recordCommandBuffer(&swapChainD->cbWrapper);
+
+ QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]);
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
+
+ if (image.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) {
+ VkImageMemoryBarrier presTrans;
+ memset(&presTrans, 0, sizeof(presTrans));
+ presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ presTrans.image = image.image;
+ presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1;
+
+ if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseNone) {
+ // was not used at all (no render pass), just transition from undefined to presentable
+ presTrans.srcAccessMask = 0;
+ presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ df->vkCmdPipelineBarrier(frame.cmdBuf,
+ VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &presTrans);
+ } else if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseTransferSource) {
+ // was used in a readback as transfer source, go back to presentable layout
+ presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+ presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ df->vkCmdPipelineBarrier(frame.cmdBuf,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &presTrans);
+ }
+ image.lastUse = QVkSwapChain::ImageResources::ScImageUseRender;
+ }
+
+ // record another timestamp, when enabled
+ if (frame.timestampQueryIndex >= 0) {
+ df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+ timestampQueryPool, frame.timestampQueryIndex + 1);
+ }
+
+ // stop recording and submit to the queue
+ Q_ASSERT(!frame.cmdFenceWaitable);
+ const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(frame.cmdBuf,
+ frame.cmdFence,
+ frame.imageSemWaitable ? &frame.imageSem : nullptr,
+ needsPresent ? &frame.drawSem : nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+
+ frame.imageSemWaitable = false;
+ frame.cmdFenceWaitable = true;
+
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ // this must be done before the Present
+ QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1));
+
+ if (needsPresent) {
+ // add the Present to the queue
+ VkPresentInfoKHR presInfo;
+ memset(&presInfo, 0, sizeof(presInfo));
+ presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ presInfo.swapchainCount = 1;
+ presInfo.pSwapchains = &swapChainD->sc;
+ presInfo.pImageIndices = &swapChainD->currentImageIndex;
+ presInfo.waitSemaphoreCount = 1;
+ presInfo.pWaitSemaphores = &frame.drawSem; // gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem;
+
+ VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo);
+ if (err != VK_SUCCESS) {
+ if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+ return QRhi::FrameOpSwapChainOutOfDate;
+ } else if (err != VK_SUBOPTIMAL_KHR) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to present: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ // mark the current swapchain buffer as unused from our side
+ frame.imageAcquired = false;
+ // and move on to the next buffer
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
+ }
+
+ swapChainD->frameCount += 1;
+ currentSwapChain = nullptr;
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb)
+{
+ // Now is the time to do things for frame N-F, where N is the current one,
+ // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that
+ // frame has completed on the GPU (due to the fence wait in beginFrame). To
+ // decide if something is safe to handle now a simple "lastActiveFrameSlot
+ // == currentFrameSlot" is sufficient (remember that e.g. with F==2
+ // currentFrameSlot goes 0, 1, 0, 1, 0, ...)
+ //
+ // With multiple swapchains on the same QRhi things get more convoluted
+ // (and currentFrameSlot strictly alternating is not true anymore) but
+ // beginNonWrapperFrame() solves that by blocking as necessary so the rest
+ // here is safe regardless.
+
+ executeDeferredReleases();
+
+ QRHI_RES(QVkCommandBuffer, cb)->resetState();
+
+ finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
+}
+
+QRhi::FrameOpResult QRhiVulkan::startCommandBuffer(VkCommandBuffer *cb)
+{
+ if (*cb) {
+ df->vkFreeCommandBuffers(dev, cmdPool, 1, cb);
+ *cb = VK_NULL_HANDLE;
+ }
+
+ VkCommandBufferAllocateInfo cmdBufInfo;
+ memset(&cmdBufInfo, 0, sizeof(cmdBufInfo));
+ cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ cmdBufInfo.commandPool = cmdPool;
+ cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ cmdBufInfo.commandBufferCount = 1;
+
+ VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to allocate frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ VkCommandBufferBeginInfo cmdBufBeginInfo;
+ memset(&cmdBufBeginInfo, 0, sizeof(cmdBufBeginInfo));
+ cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+
+ err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to begin frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endAndSubmitCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
+ VkSemaphore *waitSem, VkSemaphore *signalSem)
+{
+ VkResult err = df->vkEndCommandBuffer(cb);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to end frame command buffer: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ VkSubmitInfo submitInfo;
+ memset(&submitInfo, 0, sizeof(submitInfo));
+ submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &cb;
+ if (waitSem) {
+ submitInfo.waitSemaphoreCount = 1;
+ submitInfo.pWaitSemaphores = waitSem;
+ }
+ if (signalSem) {
+ submitInfo.signalSemaphoreCount = 1;
+ submitInfo.pSignalSemaphores = signalSem;
+ }
+ VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ submitInfo.pWaitDstStageMask = &psf;
+
+ err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence);
+ if (err != VK_SUCCESS) {
+ if (checkDeviceLost(err))
+ return QRhi::FrameOpDeviceLost;
+ else
+ qWarning("Failed to submit to graphics queue: %d", err);
+ return QRhi::FrameOpError;
+ }
+
+ return QRhi::FrameOpSuccess;
+}
+
+void QRhiVulkan::waitCommandCompletion(int frameSlot)
+{
+ for (QVkSwapChain *sc : qAsConst(swapchains)) {
+ QVkSwapChain::FrameResources &frame(sc->frameRes[frameSlot]);
+ if (frame.cmdFenceWaitable) {
+ df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &frame.cmdFence);
+ frame.cmdFenceWaitable = false;
+ }
+ }
+}
+
+QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb)
+{
+ QRhi::FrameOpResult cbres = startCommandBuffer(&ofr.cbWrapper.cb);
+ if (cbres != QRhi::FrameOpSuccess)
+ return cbres;
+
+ // Switch to the next slot manually. Swapchains do not know about this
+ // which is good. So for example a - unusual but possible - onscreen,
+ // onscreen, offscreen, onscreen, onscreen, onscreen sequence of
+ // begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the
+ // offscreen frame is synchronous in the sense that we wait for execution
+ // to complete in endFrame, and so no resources used in that frame are busy
+ // anymore in the next frame.
+ currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT;
+ // except that this gets complicated with multiple swapchains so make sure
+ // any pending commands have finished for the frame slot we are going to use
+ if (swapchains.count() > 1)
+ waitCommandCompletion(currentFrameSlot);
+
+ prepareNewFrame(&ofr.cbWrapper);
+ ofr.active = true;
+
+ *cb = &ofr.cbWrapper;
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame()
+{
+ Q_ASSERT(ofr.active);
+ ofr.active = false;
+
+ recordCommandBuffer(&ofr.cbWrapper);
+
+ if (!ofr.cmdFence) {
+ VkFenceCreateInfo fenceInfo;
+ memset(&fenceInfo, 0, sizeof(fenceInfo));
+ fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create command buffer fence: %d", err);
+ return QRhi::FrameOpError;
+ }
+ }
+
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(ofr.cbWrapper.cb, ofr.cmdFence, nullptr, nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+
+ // wait for completion
+ df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX);
+ df->vkResetFences(dev, 1, &ofr.cmdFence);
+
+ // Here we know that executing the host-side reads for this (or any
+ // previous) frame is safe since we waited for completion above.
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiVulkan::finish()
+{
+ QVkSwapChain *swapChainD = nullptr;
+ if (inFrame) {
+ // There is either a swapchain or an offscreen frame on-going.
+ // End command recording and submit what we have.
+ VkCommandBuffer cb;
+ if (ofr.active) {
+ Q_ASSERT(!currentSwapChain);
+ recordCommandBuffer(&ofr.cbWrapper);
+ cb = ofr.cbWrapper.cb;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = currentSwapChain;
+ recordCommandBuffer(&swapChainD->cbWrapper);
+ cb = swapChainD->cbWrapper.cb;
+ }
+ QRhi::FrameOpResult submitres = endAndSubmitCommandBuffer(cb, VK_NULL_HANDLE, nullptr, nullptr);
+ if (submitres != QRhi::FrameOpSuccess)
+ return submitres;
+ }
+
+ df->vkQueueWaitIdle(gfxQueue);
+
+ if (inFrame) {
+ // Allocate and begin recording on a new command buffer.
+ if (ofr.active)
+ startCommandBuffer(&ofr.cbWrapper.cb);
+ else
+ startCommandBuffer(&swapChainD->frameRes[swapChainD->currentFrameSlot].cmdBuf);
+ }
+
+ executeDeferredReleases(true);
+ finishActiveReadbacks(true);
+
+ return QRhi::FrameOpSuccess;
+}
+
+static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage)
+{
+ QRhiPassResourceTracker::UsageState u;
+ u.layout = 0; // unused with buffers
+ u.access = bufUsage.access;
+ u.stage = bufUsage.stage;
+ return u;
+}
+
+static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage)
+{
+ QRhiPassResourceTracker::UsageState u;
+ u.layout = texUsage.layout;
+ u.access = texUsage.access;
+ u.stage = texUsage.stage;
+ return u;
+}
+
+void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD)
+{
+ rtD->lastActiveFrameSlot = currentFrameSlot;
+ rtD->d.rp->lastActiveFrameSlot = currentFrameSlot;
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+ const QVector<QRhiColorAttachment> colorAttachments = rtD->m_desc.colorAttachments();
+ for (const QRhiColorAttachment &colorAttachment : colorAttachments) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachment.texture());
+ QVkTexture *resolveTexD = QRHI_RES(QVkTexture, colorAttachment.resolveTexture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachment.renderBuffer());
+ if (texD) {
+ trackedRegisterTexture(&passResTracker, texD,
+ QRhiPassResourceTracker::TexColorOutput,
+ QRhiPassResourceTracker::TexColorOutputStage);
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (rbD) {
+ // Won't register rbD->backingTexture because it cannot be used for
+ // anything in a renderpass, its use makes only sense in
+ // combination with a resolveTexture.
+ rbD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ if (resolveTexD) {
+ trackedRegisterTexture(&passResTracker, resolveTexD,
+ QRhiPassResourceTracker::TexColorOutput,
+ QRhiPassResourceTracker::TexColorOutputStage);
+ resolveTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+ if (rtD->m_desc.depthStencilBuffer())
+ QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtD->m_desc.depthTexture()) {
+ QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture());
+ trackedRegisterTexture(&passResTracker, depthTexD,
+ QRhiPassResourceTracker::TexDepthOutput,
+ QRhiPassResourceTracker::TexDepthOutputStage);
+ depthTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+}
+
+void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+
+ enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+
+ // Insert a TransitionPassResources into the command stream, pointing to
+ // the tracker this pass is going to use. That's how we generate the
+ // barriers later during recording the real VkCommandBuffer, right before
+ // the vkCmdBeginRenderPass.
+ enqueueTransitionPassResources(cbD);
+
+ QVkRenderTargetData *rtD = nullptr;
+ switch (rt->resourceType()) {
+ case QRhiResource::RenderTarget:
+ rtD = &QRHI_RES(QVkReferenceRenderTarget, rt)->d;
+ rtD->rp->lastActiveFrameSlot = currentFrameSlot;
+ Q_ASSERT(currentSwapChain);
+ currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse =
+ QVkSwapChain::ImageResources::ScImageUseRender;
+ break;
+ case QRhiResource::TextureRenderTarget:
+ {
+ QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt);
+ rtD = &rtTex->d;
+ activateTextureRenderTarget(cbD, rtTex);
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ cbD->recordingPass = QVkCommandBuffer::RenderPass;
+ cbD->currentTarget = rt;
+
+ // No copy operations or image layout transitions allowed after this point
+ // (up until endPass) as we are going to begin the renderpass.
+
+ VkRenderPassBeginInfo rpBeginInfo;
+ memset(&rpBeginInfo, 0, sizeof(rpBeginInfo));
+ rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ rpBeginInfo.renderPass = rtD->rp->rp;
+ rpBeginInfo.framebuffer = rtD->fb;
+ rpBeginInfo.renderArea.extent.width = rtD->pixelSize.width();
+ rpBeginInfo.renderArea.extent.height = rtD->pixelSize.height();
+
+ QVarLengthArray<VkClearValue, 4> cvs;
+ for (int i = 0; i < rtD->colorAttCount; ++i) {
+ VkClearValue cv;
+ cv.color = { { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
+ float(colorClearValue.alphaF()) } };
+ cvs.append(cv);
+ }
+ for (int i = 0; i < rtD->dsAttCount; ++i) {
+ VkClearValue cv;
+ cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
+ cvs.append(cv);
+ }
+ for (int i = 0; i < rtD->resolveAttCount; ++i) {
+ VkClearValue cv;
+ cv.color = { { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()),
+ float(colorClearValue.alphaF()) } };
+ cvs.append(cv);
+ }
+ rpBeginInfo.clearValueCount = cvs.count();
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass;
+ cmd.args.beginRenderPass.desc = rpBeginInfo;
+ cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.count();
+ cbD->pools.clearValue.append(cvs.constData(), cvs.count());
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::EndRenderPass;
+ cbD->commands.append(cmd);
+
+ cbD->recordingPass = QVkCommandBuffer::NoPass;
+ cbD->currentTarget = nullptr;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+
+ enqueueTransitionPassResources(cbD);
+
+ cbD->recordingPass = QVkCommandBuffer::ComputePass;
+}
+
+void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
+
+ cbD->recordingPass = QVkCommandBuffer::NoPass;
+
+ if (resourceUpdates)
+ enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+ QVkComputePipeline *psD = QRHI_RES(QVkComputePipeline, ps);
+ Q_ASSERT(psD->pipeline);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
+
+ if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindPipeline;
+ cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE;
+ cmd.args.bindPipeline.pipeline = psD->pipeline;
+ cbD->commands.append(cmd);
+
+ cbD->currentGraphicsPipeline = nullptr;
+ cbD->currentComputePipeline = ps;
+ cbD->currentPipelineGeneration = psD->generation;
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::Dispatch;
+ cmd.args.dispatch.x = x;
+ cmd.args.dispatch.y = y;
+ cmd.args.dispatch.z = z;
+ cbD->commands.append(cmd);
+}
+
+VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv)
+{
+ VkShaderModuleCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ shaderInfo.codeSize = spirv.size();
+ shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData());
+ VkShaderModule shaderModule;
+ VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create shader module: %d", err);
+ return VK_NULL_HANDLE;
+ }
+ return shaderModule;
+}
+
+bool QRhiVulkan::ensurePipelineCache()
+{
+ if (pipelineCache)
+ return true;
+
+ VkPipelineCacheCreateInfo pipelineCacheInfo;
+ memset(&pipelineCacheInfo, 0, sizeof(pipelineCacheInfo));
+ pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+ VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create pipeline cache: %d", err);
+ return false;
+ }
+ return true;
+}
+
+void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx)
+{
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
+
+ QVarLengthArray<VkDescriptorBufferInfo, 4> bufferInfos;
+ QVarLengthArray<VkDescriptorImageInfo, 4> imageInfos;
+ QVarLengthArray<VkWriteDescriptorSet, 8> writeInfos;
+
+ const bool updateAll = descSetIdx < 0;
+ int frameSlot = updateAll ? 0 : descSetIdx;
+ while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) {
+ srbD->boundResourceData[frameSlot].resize(srbD->sortedBindings.count());
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]);
+
+ VkWriteDescriptorSet writeInfo;
+ memset(&writeInfo, 0, sizeof(writeInfo));
+ writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ writeInfo.dstSet = srbD->descSets[frameSlot];
+ writeInfo.dstBinding = b->binding;
+ writeInfo.descriptorCount = 1;
+
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
+ : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ QRhiBuffer *buf = b->u.ubuf.buf;
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf);
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ VkDescriptorBufferInfo bufInfo;
+ bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0];
+ bufInfo.offset = b->u.ubuf.offset;
+ bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size;
+ // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads
+ Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset);
+ bufferInfos.append(bufInfo);
+ writeInfo.pBufferInfo = &bufferInfos.last();
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
+ QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ VkDescriptorImageInfo imageInfo;
+ imageInfo.sampler = samplerD->sampler;
+ imageInfo.imageView = texD->imageView;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ imageInfos.append(imageInfo);
+ writeInfo.pImageInfo = &imageInfos.last();
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
+ VkImageView view = texD->imageViewForLevel(b->u.simage.level);
+ if (view) {
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ VkDescriptorImageInfo imageInfo;
+ imageInfo.sampler = VK_NULL_HANDLE;
+ imageInfo.imageView = view;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ imageInfos.append(imageInfo);
+ writeInfo.pImageInfo = &imageInfos.last();
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
+ writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ VkDescriptorBufferInfo bufInfo;
+ bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0];
+ bufInfo.offset = b->u.ubuf.offset;
+ bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size;
+ bufferInfos.append(bufInfo);
+ writeInfo.pBufferInfo = &bufferInfos.last();
+ }
+ break;
+ default:
+ continue;
+ }
+
+ writeInfos.append(writeInfo);
+ }
+ ++frameSlot;
+ }
+
+ df->vkUpdateDescriptorSets(dev, writeInfos.count(), writeInfos.constData(), 0, nullptr);
+}
+
+static inline bool accessIsWrite(VkAccessFlags access)
+{
+ return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0
+ || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0
+ || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0
+ || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0
+ || (access & VK_ACCESS_HOST_WRITE_BIT) != 0
+ || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0;
+}
+
+void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot,
+ VkAccessFlags access, VkPipelineStageFlags stage)
+{
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+ Q_ASSERT(access && stage);
+ QVkBuffer::UsageState &s(bufD->usageState[slot]);
+ if (!s.stage) {
+ s.access = access;
+ s.stage = stage;
+ return;
+ }
+
+ if (s.access == access && s.stage == stage) {
+ // No need to flood with unnecessary read-after-read barriers.
+ // Write-after-write is a different matter, however.
+ if (!accessIsWrite(access))
+ return;
+ }
+
+ VkBufferMemoryBarrier bufMemBarrier;
+ memset(&bufMemBarrier, 0, sizeof(bufMemBarrier));
+ bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.srcAccessMask = s.access;
+ bufMemBarrier.dstAccessMask = access;
+ bufMemBarrier.buffer = bufD->buffers[slot];
+ bufMemBarrier.size = VK_WHOLE_SIZE;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BufferBarrier;
+ cmd.args.bufferBarrier.srcStageMask = s.stage;
+ cmd.args.bufferBarrier.dstStageMask = stage;
+ cmd.args.bufferBarrier.desc = bufMemBarrier;
+ cbD->commands.append(cmd);
+
+ s.access = access;
+ s.stage = stage;
+}
+
+void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD,
+ VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage)
+{
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+ Q_ASSERT(layout && access && stage);
+ QVkTexture::UsageState &s(texD->usageState);
+ if (s.access == access && s.stage == stage && s.layout == layout) {
+ if (!accessIsWrite(access))
+ return;
+ }
+
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = !isDepthTextureFormat(texD->m_format)
+ ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
+ barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
+ barrier.newLayout = layout;
+ barrier.srcAccessMask = s.access; // may be 0 but that's fine
+ barrier.dstAccessMask = access;
+ barrier.image = texD->image;
+
+ VkPipelineStageFlags srcStage = s.stage;
+ // stage mask cannot be 0
+ if (!srcStage)
+ srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
+ cmd.args.imageBarrier.srcStageMask = srcStage;
+ cmd.args.imageBarrier.dstStageMask = stage;
+ cmd.args.imageBarrier.desc = barrier;
+ cbD->commands.append(cmd);
+
+ s.layout = layout;
+ s.access = access;
+ s.stage = stage;
+}
+
+void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ VkAccessFlags srcAccess, VkAccessFlags dstAccess,
+ VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
+ int startLayer, int layerCount,
+ int startLevel, int levelCount)
+{
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.baseMipLevel = startLevel;
+ barrier.subresourceRange.levelCount = levelCount;
+ barrier.subresourceRange.baseArrayLayer = startLayer;
+ barrier.subresourceRange.layerCount = layerCount;
+ barrier.oldLayout = oldLayout;
+ barrier.newLayout = newLayout;
+ barrier.srcAccessMask = srcAccess;
+ barrier.dstAccessMask = dstAccess;
+ barrier.image = image;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::ImageBarrier;
+ cmd.args.imageBarrier.srcStageMask = srcStage;
+ cmd.args.imageBarrier.dstStageMask = dstStage;
+ cmd.args.imageBarrier.desc = barrier;
+ cbD->commands.append(cmd);
+}
+
+VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const
+{
+ VkDeviceSize size = 0;
+ const qsizetype imageSizeBytes = subresDesc.image().isNull() ?
+ subresDesc.data().size() : subresDesc.image().sizeInBytes();
+ if (imageSizeBytes > 0)
+ size += aligned(imageSizeBytes, texbufAlign);
+ return size;
+}
+
+void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &subresDesc,
+ size_t *curOfs, void *mp,
+ BufferImageCopyList *copyInfos)
+{
+ qsizetype copySizeBytes = 0;
+ qsizetype imageSizeBytes = 0;
+ const void *src = nullptr;
+
+ VkBufferImageCopy copyInfo;
+ memset(&copyInfo, 0, sizeof(copyInfo));
+ copyInfo.bufferOffset = *curOfs;
+ copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copyInfo.imageSubresource.mipLevel = level;
+ copyInfo.imageSubresource.baseArrayLayer = layer;
+ copyInfo.imageSubresource.layerCount = 1;
+ copyInfo.imageExtent.depth = 1;
+
+ const QByteArray rawData = subresDesc.data();
+ const QPoint dp = subresDesc.destinationTopLeft();
+ QImage image = subresDesc.image();
+ if (!image.isNull()) {
+ copySizeBytes = imageSizeBytes = image.sizeInBytes();
+ QSize size = image.size();
+ src = image.constBits();
+ // Scanlines in QImage are 4 byte aligned so bpl must
+ // be taken into account for bufferRowLength.
+ int bpc = qMax(1, image.depth() / 8);
+ // this is in pixels, not bytes, to make it more complicated...
+ copyInfo.bufferRowLength = image.bytesPerLine() / bpc;
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const int sx = subresDesc.sourceTopLeft().x();
+ const int sy = subresDesc.sourceTopLeft().y();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ if (image.depth() == 32) {
+ // The staging buffer will get the full image
+ // regardless, just adjust the vk
+ // buffer-to-image copy start offset.
+ copyInfo.bufferOffset += sy * image.bytesPerLine() + sx * 4;
+ // bufferRowLength remains set to the original image's width
+ } else {
+ image = image.copy(sx, sy, size.width(), size.height());
+ src = image.constBits();
+ // The staging buffer gets the slice only. The rest of the
+ // space reserved for this mip will be unused.
+ copySizeBytes = image.sizeInBytes();
+ bpc = qMax(1, image.depth() / 8);
+ copyInfo.bufferRowLength = image.bytesPerLine() / bpc;
+ }
+ }
+ copyInfo.imageOffset.x = dp.x();
+ copyInfo.imageOffset.y = dp.y();
+ copyInfo.imageExtent.width = size.width();
+ copyInfo.imageExtent.height = size.height();
+ copyInfos->append(copyInfo);
+ } else if (!rawData.isEmpty() && isCompressedFormat(texD->m_format)) {
+ copySizeBytes = imageSizeBytes = rawData.size();
+ src = rawData.constData();
+ QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
+ const int subresw = size.width();
+ const int subresh = size.height();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ const int w = size.width();
+ const int h = size.height();
+ QSize blockDim;
+ compressedFormatInfo(texD->m_format, QSize(w, h), nullptr, nullptr, &blockDim);
+ // x and y must be multiples of the block width and height
+ copyInfo.imageOffset.x = aligned(dp.x(), blockDim.width());
+ copyInfo.imageOffset.y = aligned(dp.y(), blockDim.height());
+ // width and height must be multiples of the block width and height
+ // or x + width and y + height must equal the subresource width and height
+ copyInfo.imageExtent.width = dp.x() + w == subresw ? w : aligned(w, blockDim.width());
+ copyInfo.imageExtent.height = dp.y() + h == subresh ? h : aligned(h, blockDim.height());
+ copyInfos->append(copyInfo);
+ } else if (!rawData.isEmpty()) {
+ copySizeBytes = imageSizeBytes = rawData.size();
+ src = rawData.constData();
+ QSize size = q->sizeForMipLevel(level, texD->m_pixelSize);
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+ copyInfo.imageOffset.x = dp.x();
+ copyInfo.imageOffset.y = dp.y();
+ copyInfo.imageExtent.width = size.width();
+ copyInfo.imageExtent.height = size.height();
+ copyInfos->append(copyInfo);
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ }
+
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs, src, copySizeBytes);
+ *curOfs += aligned(imageSizeBytes, texbufAlign);
+}
+
+void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
+{
+ QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : ud->dynamicBufferUpdates) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ bufD->pendingDynamicUpdates[i].append(u);
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::StaticBufferUpload &u : ud->staticBufferUploads) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf);
+ Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+ Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+
+ if (!bufD->stagingBuffers[currentFrameSlot]) {
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ // must cover the entire buffer - this way multiple, partial updates per frame
+ // are supported even when the staging buffer is reused (Static)
+ bufferInfo.size = bufD->m_size;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
+ &bufD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
+ if (err == VK_SUCCESS) {
+ bufD->stagingAllocations[currentFrameSlot] = allocation;
+ QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, bufD->m_size));
+ } else {
+ qWarning("Failed to create staging buffer of size %d: %d", bufD->m_size, err);
+ continue;
+ }
+ }
+
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(bufD->stagingAllocations[currentFrameSlot]);
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map buffer: %d", err);
+ continue;
+ }
+ memcpy(static_cast<uchar *>(p) + u.offset, u.data.constData(), u.data.size());
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ vmaFlushAllocation(toVmaAllocator(allocator), a, u.offset, u.data.size());
+
+ trackedBufferBarrier(cbD, bufD, 0,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ VkBufferCopy copyInfo;
+ memset(&copyInfo, 0, sizeof(copyInfo));
+ copyInfo.srcOffset = u.offset;
+ copyInfo.dstOffset = u.offset;
+ copyInfo.size = u.data.size();
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyBuffer;
+ cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot];
+ cmd.args.copyBuffer.dst = bufD->buffers[0];
+ cmd.args.copyBuffer.desc = copyInfo;
+ cbD->commands.append(cmd);
+
+ // Where's the barrier for read-after-write? (assuming the common case
+ // of binding this buffer as vertex/index, or, less likely, as uniform
+ // buffer, in a renderpass later on) That is handled by the pass
+ // resource tracking: the appropriate pipeline barrier will be
+ // generated and recorded right before the renderpass, that binds this
+ // buffer in one of its commands, gets its BeginRenderPass recorded.
+
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+
+ if (bufD->m_type == QRhiBuffer::Immutable) {
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot];
+ e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot];
+ bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
+ bufD->stagingAllocations[currentFrameSlot] = nullptr;
+ releaseQueue.append(e);
+ QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot));
+ }
+ }
+
+ for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) {
+ if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+ QVkTexture *utexD = QRHI_RES(QVkTexture, u.upload.tex);
+ // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos
+ VkDeviceSize stagingSize = 0;
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(u.upload.subresDesc[layer][level]))
+ stagingSize += subresUploadByteSize(subresDesc);
+ }
+ }
+
+ Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]);
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = stagingSize;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo,
+ &utexD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image staging buffer of size %d: %d", int(stagingSize), err);
+ continue;
+ }
+ utexD->stagingAllocations[currentFrameSlot] = allocation;
+ QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, stagingSize));
+
+ BufferImageCopyList copyInfos;
+ size_t curOfs = 0;
+ void *mp = nullptr;
+ VmaAllocation a = toVmaAllocation(utexD->stagingAllocations[currentFrameSlot]);
+ err = vmaMapMemory(toVmaAllocator(allocator), a, &mp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map image data: %d", err);
+ continue;
+ }
+
+ for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) {
+ for (int level = 0; level < QRhi::MAX_LEVELS; ++level) {
+ const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.upload.subresDesc[layer][level]);
+ if (srd.isEmpty())
+ continue;
+ for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(srd)) {
+ prepareUploadSubres(utexD, layer, level,
+ subresDesc, &curOfs, mp, &copyInfos);
+ }
+ }
+ }
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ vmaFlushAllocation(toVmaAllocator(allocator), a, 0, stagingSize);
+
+ trackedImageBarrier(cbD, utexD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyBufferToImage;
+ cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot];
+ cmd.args.copyBufferToImage.dst = utexD->image;
+ cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ cmd.args.copyBufferToImage.count = copyInfos.count();
+ cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.count();
+ cbD->pools.bufferImageCopy.append(copyInfos.constData(), copyInfos.count());
+ cbD->commands.append(cmd);
+
+ // no reuse of staging, this is intentional
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer;
+ e.lastActiveFrameSlot = currentFrameSlot;
+ e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot];
+ e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot];
+ utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE;
+ utexD->stagingAllocations[currentFrameSlot] = nullptr;
+ releaseQueue.append(e);
+ QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot));
+
+ // Similarly to buffers, transitioning away from DST is done later,
+ // when a renderpass using the texture is encountered.
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+ Q_ASSERT(u.copy.src && u.copy.dst);
+ if (u.copy.src == u.copy.dst) {
+ qWarning("Texture copy with matching source and destination is not supported");
+ continue;
+ }
+ QVkTexture *srcD = QRHI_RES(QVkTexture, u.copy.src);
+ QVkTexture *dstD = QRHI_RES(QVkTexture, u.copy.dst);
+
+ VkImageCopy region;
+ memset(&region, 0, sizeof(region));
+
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.mipLevel = u.copy.desc.sourceLevel();
+ region.srcSubresource.baseArrayLayer = u.copy.desc.sourceLayer();
+ region.srcSubresource.layerCount = 1;
+
+ region.srcOffset.x = u.copy.desc.sourceTopLeft().x();
+ region.srcOffset.y = u.copy.desc.sourceTopLeft().y();
+
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.mipLevel = u.copy.desc.destinationLevel();
+ region.dstSubresource.baseArrayLayer = u.copy.desc.destinationLayer();
+ region.dstSubresource.layerCount = 1;
+
+ region.dstOffset.x = u.copy.desc.destinationTopLeft().x();
+ region.dstOffset.y = u.copy.desc.destinationTopLeft().y();
+
+ const QSize size = u.copy.desc.pixelSize().isEmpty() ? srcD->m_pixelSize : u.copy.desc.pixelSize();
+ region.extent.width = size.width();
+ region.extent.height = size.height();
+ region.extent.depth = 1;
+
+ trackedImageBarrier(cbD, srcD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ trackedImageBarrier(cbD, dstD, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImage;
+ cmd.args.copyImage.src = srcD->image;
+ cmd.args.copyImage.srcLayout = srcD->usageState.layout;
+ cmd.args.copyImage.dst = dstD->image;
+ cmd.args.copyImage.dstLayout = dstD->usageState.layout;
+ cmd.args.copyImage.desc = region;
+ cbD->commands.append(cmd);
+
+ srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot;
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+ ActiveReadback aRb;
+ aRb.activeFrameSlot = currentFrameSlot;
+ aRb.desc = u.read.rb;
+ aRb.result = u.read.result;
+
+ QVkTexture *texD = QRHI_RES(QVkTexture, u.read.rb.texture());
+ QVkSwapChain *swapChainD = nullptr;
+ if (texD) {
+ if (texD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ qWarning("Multisample texture cannot be read back");
+ continue;
+ }
+ aRb.pixelSize = u.read.rb.level() > 0 ? q->sizeForMipLevel(u.read.rb.level(), texD->m_pixelSize)
+ : texD->m_pixelSize;
+ aRb.format = texD->m_format;
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ } else {
+ Q_ASSERT(currentSwapChain);
+ swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain);
+ if (!swapChainD->supportsReadback) {
+ qWarning("Swapchain does not support readback");
+ continue;
+ }
+ aRb.pixelSize = swapChainD->pixelSize;
+ aRb.format = colorTextureFormatFromVkFormat(swapChainD->colorFormat, nullptr);
+ if (aRb.format == QRhiTexture::UnknownFormat)
+ continue;
+
+ // Multisample swapchains need nothing special since resolving
+ // happens when ending a renderpass.
+ }
+ textureFormatInfo(aRb.format, aRb.pixelSize, nullptr, &aRb.bufSize);
+
+ // Create a host visible buffer.
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = aRb.bufSize;
+ bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU;
+
+ VmaAllocation allocation;
+ VkResult err = vmaCreateBuffer(toVmaAllocator(allocator), &bufferInfo, &allocInfo, &aRb.buf, &allocation, nullptr);
+ if (err == VK_SUCCESS) {
+ aRb.bufAlloc = allocation;
+ QRHI_PROF_F(newReadbackBuffer(quint64(aRb.buf),
+ texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD),
+ aRb.bufSize));
+ } else {
+ qWarning("Failed to create readback buffer of size %u: %d", aRb.bufSize, err);
+ continue;
+ }
+
+ // Copy from the (optimal and not host visible) image into the buffer.
+ VkBufferImageCopy copyDesc;
+ memset(&copyDesc, 0, sizeof(copyDesc));
+ copyDesc.bufferOffset = 0;
+ copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ copyDesc.imageSubresource.mipLevel = u.read.rb.level();
+ copyDesc.imageSubresource.baseArrayLayer = u.read.rb.layer();
+ copyDesc.imageSubresource.layerCount = 1;
+ copyDesc.imageExtent.width = aRb.pixelSize.width();
+ copyDesc.imageExtent.height = aRb.pixelSize.height();
+ copyDesc.imageExtent.depth = 1;
+
+ if (texD) {
+ trackedImageBarrier(cbD, texD, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
+ cmd.args.copyImageToBuffer.src = texD->image;
+ cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout;
+ cmd.args.copyImageToBuffer.dst = aRb.buf;
+ cmd.args.copyImageToBuffer.desc = copyDesc;
+ cbD->commands.append(cmd);
+ } else {
+ // use the swapchain image
+ QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]);
+ VkImage image = imageRes.image;
+ if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseTransferSource) {
+ if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) {
+ qWarning("Attempted to read back undefined swapchain image content, "
+ "results are undefined. (do a render pass first)");
+ }
+ subresourceBarrier(cbD, image,
+ VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT,
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, 1,
+ 0, 1);
+ imageRes.lastUse = QVkSwapChain::ImageResources::ScImageUseTransferSource;
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer;
+ cmd.args.copyImageToBuffer.src = image;
+ cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ cmd.args.copyImageToBuffer.dst = aRb.buf;
+ cmd.args.copyImageToBuffer.desc = copyDesc;
+ cbD->commands.append(cmd);
+ }
+
+ activeReadbacks.append(aRb);
+ } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::MipGen) {
+ QVkTexture *utexD = QRHI_RES(QVkTexture, u.mipgen.tex);
+ Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips));
+ int w = utexD->m_pixelSize.width();
+ int h = utexD->m_pixelSize.height();
+
+ VkImageLayout origLayout = utexD->usageState.layout;
+ VkAccessFlags origAccess = utexD->usageState.access;
+ VkPipelineStageFlags origStage = utexD->usageState.stage;
+ if (!origStage)
+ origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+
+ for (uint level = 1; level < utexD->mipLevelCount; ++level) {
+ if (level == 1) {
+ subresourceBarrier(cbD, utexD->image,
+ origLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ origAccess, VK_ACCESS_TRANSFER_READ_BIT,
+ origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level - 1, 1);
+ } else {
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level - 1, 1);
+ }
+
+ subresourceBarrier(cbD, utexD->image,
+ origLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ origAccess, VK_ACCESS_TRANSFER_WRITE_BIT,
+ origStage, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ u.mipgen.layer, 1,
+ level, 1);
+
+ VkImageBlit region;
+ memset(&region, 0, sizeof(region));
+
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.mipLevel = level - 1;
+ region.srcSubresource.baseArrayLayer = u.mipgen.layer;
+ region.srcSubresource.layerCount = 1;
+
+ region.srcOffsets[1].x = qMax(1, w);
+ region.srcOffsets[1].y = qMax(1, h);
+ region.srcOffsets[1].z = 1;
+
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.mipLevel = level;
+ region.dstSubresource.baseArrayLayer = u.mipgen.layer;
+ region.dstSubresource.layerCount = 1;
+
+ region.dstOffsets[1].x = qMax(1, w >> 1);
+ region.dstOffsets[1].y = qMax(1, h >> 1);
+ region.dstOffsets[1].z = 1;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BlitImage;
+ cmd.args.blitImage.src = utexD->image;
+ cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ cmd.args.blitImage.dst = utexD->image;
+ cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ cmd.args.blitImage.filter = VK_FILTER_LINEAR;
+ cmd.args.blitImage.desc = region;
+ cbD->commands.append(cmd);
+
+ w >>= 1;
+ h >>= 1;
+ }
+
+ if (utexD->mipLevelCount > 1) {
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, origLayout,
+ VK_ACCESS_TRANSFER_READ_BIT, origAccess,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
+ u.mipgen.layer, 1,
+ 0, utexD->mipLevelCount - 1);
+ subresourceBarrier(cbD, utexD->image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, origLayout,
+ VK_ACCESS_TRANSFER_WRITE_BIT, origAccess,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, origStage,
+ u.mipgen.layer, 1,
+ utexD->mipLevelCount - 1, 1);
+ }
+
+ utexD->lastActiveFrameSlot = currentFrameSlot;
+ }
+ }
+
+ ud->free();
+}
+
+void QRhiVulkan::executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD)
+{
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> &updates(bufD->pendingDynamicUpdates[currentFrameSlot]);
+ if (updates.isEmpty())
+ return;
+
+ Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(bufD->allocations[currentFrameSlot]);
+ // The vmaMap/Unmap are basically a no-op when persistently mapped since it
+ // refcounts; this is great because we don't need to care if the allocation
+ // was created as persistently mapped or not.
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map buffer: %d", err);
+ return;
+ }
+ int changeBegin = -1;
+ int changeEnd = -1;
+ for (const QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate &u : updates) {
+ Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf));
+ memcpy(static_cast<char *>(p) + u.offset, u.data.constData(), u.data.size());
+ if (changeBegin == -1 || u.offset < changeBegin)
+ changeBegin = u.offset;
+ if (changeEnd == -1 || u.offset + u.data.size() > changeEnd)
+ changeEnd = u.offset + u.data.size();
+ }
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+ if (changeBegin >= 0)
+ vmaFlushAllocation(toVmaAllocator(allocator), a, changeBegin, changeEnd - changeBegin);
+
+ updates.clear();
+}
+
+static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.buffers[i], toVmaAllocation(e.buffer.allocations[i]));
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.buffer.stagingBuffers[i], toVmaAllocation(e.buffer.stagingAllocations[i]));
+ }
+}
+
+static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
+{
+ df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr);
+ df->vkDestroyImage(dev, e.renderBuffer.image, nullptr);
+ df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr);
+}
+
+static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator)
+{
+ df->vkDestroyImageView(dev, e.texture.imageView, nullptr);
+ vmaDestroyImage(toVmaAllocator(allocator), e.texture.image, toVmaAllocation(e.texture.allocation));
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.texture.stagingBuffers[i], toVmaAllocation(e.texture.stagingAllocations[i]));
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
+ if (e.texture.extraImageViews[i])
+ df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr);
+ }
+}
+
+static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df)
+{
+ df->vkDestroySampler(dev, e.sampler.sampler, nullptr);
+}
+
+void QRhiVulkan::executeDeferredReleases(bool forced)
+{
+ for (int i = releaseQueue.count() - 1; i >= 0; --i) {
+ const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]);
+ if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) {
+ switch (e.type) {
+ case QRhiVulkan::DeferredReleaseEntry::Pipeline:
+ df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr);
+ df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings:
+ df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr);
+ if (e.shaderResourceBindings.poolIndex >= 0) {
+ descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1;
+ Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0);
+ }
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Buffer:
+ qrhivk_releaseBuffer(e, allocator);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::RenderBuffer:
+ qrhivk_releaseRenderBuffer(e, dev, df);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Texture:
+ qrhivk_releaseTexture(e, dev, df, allocator);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::Sampler:
+ qrhivk_releaseSampler(e, dev, df);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget:
+ df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr);
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr);
+ df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
+ }
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::RenderPass:
+ df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
+ break;
+ case QRhiVulkan::DeferredReleaseEntry::StagingBuffer:
+ vmaDestroyBuffer(toVmaAllocator(allocator), e.stagingBuffer.stagingBuffer, toVmaAllocation(e.stagingBuffer.stagingAllocation));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ releaseQueue.removeAt(i);
+ }
+ }
+}
+
+void QRhiVulkan::finishActiveReadbacks(bool forced)
+{
+ QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+
+ for (int i = activeReadbacks.count() - 1; i >= 0; --i) {
+ const QRhiVulkan::ActiveReadback &aRb(activeReadbacks[i]);
+ if (forced || currentFrameSlot == aRb.activeFrameSlot || aRb.activeFrameSlot < 0) {
+ aRb.result->format = aRb.format;
+ aRb.result->pixelSize = aRb.pixelSize;
+ aRb.result->data.resize(aRb.bufSize);
+ void *p = nullptr;
+ VmaAllocation a = toVmaAllocation(aRb.bufAlloc);
+ VkResult err = vmaMapMemory(toVmaAllocator(allocator), a, &p);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to map readback buffer: %d", err);
+ continue;
+ }
+ memcpy(aRb.result->data.data(), p, aRb.bufSize);
+ vmaUnmapMemory(toVmaAllocator(allocator), a);
+
+ vmaDestroyBuffer(toVmaAllocator(allocator), aRb.buf, a);
+ QRHI_PROF_F(releaseReadbackBuffer(quint64(aRb.buf)));
+
+ if (aRb.result->completed)
+ completedCallbacks.append(aRb.result->completed);
+
+ activeReadbacks.removeAt(i);
+ }
+ }
+
+ for (auto f : completedCallbacks)
+ f();
+}
+
+static struct {
+ VkSampleCountFlagBits mask;
+ int count;
+} qvk_sampleCounts[] = {
+ // keep this sorted by 'count'
+ { VK_SAMPLE_COUNT_1_BIT, 1 },
+ { VK_SAMPLE_COUNT_2_BIT, 2 },
+ { VK_SAMPLE_COUNT_4_BIT, 4 },
+ { VK_SAMPLE_COUNT_8_BIT, 8 },
+ { VK_SAMPLE_COUNT_16_BIT, 16 },
+ { VK_SAMPLE_COUNT_32_BIT, 32 },
+ { VK_SAMPLE_COUNT_64_BIT, 64 }
+};
+
+QVector<int> QRhiVulkan::supportedSampleCounts() const
+{
+ const VkPhysicalDeviceLimits *limits = &physDevProperties.limits;
+ VkSampleCountFlags color = limits->framebufferColorSampleCounts;
+ VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
+ VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
+ QVector<int> result;
+
+ for (size_t i = 0; i < sizeof(qvk_sampleCounts) / sizeof(qvk_sampleCounts[0]); ++i) {
+ if ((color & qvk_sampleCounts[i].mask)
+ && (depth & qvk_sampleCounts[i].mask)
+ && (stencil & qvk_sampleCounts[i].mask))
+ {
+ result.append(qvk_sampleCounts[i].count);
+ }
+ }
+
+ return result;
+}
+
+VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount)
+{
+ // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1.
+ sampleCount = qBound(1, sampleCount, 64);
+
+ if (!supportedSampleCounts().contains(sampleCount)) {
+ qWarning("Attempted to set unsupported sample count %d", sampleCount);
+ return VK_SAMPLE_COUNT_1_BIT;
+ }
+
+ for (size_t i = 0; i < sizeof(qvk_sampleCounts) / sizeof(qvk_sampleCounts[0]); ++i) {
+ if (qvk_sampleCounts[i].count == sampleCount)
+ return qvk_sampleCounts[i].mask;
+ }
+
+ Q_UNREACHABLE();
+ return VK_SAMPLE_COUNT_1_BIT;
+}
+
+void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD)
+{
+ cbD->passResTrackers.append(QRhiPassResourceTracker());
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources;
+ cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.count() - 1;
+ cbD->commands.append(cmd);
+ cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1;
+}
+
+void QRhiVulkan::recordCommandBuffer(QVkCommandBuffer *cbD)
+{
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass);
+
+ for (QVkCommandBuffer::Command &cmd : cbD->commands) {
+ switch (cmd.cmd) {
+ case QVkCommandBuffer::Command::CopyBuffer:
+ df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst,
+ 1, &cmd.args.copyBuffer.desc);
+ break;
+ case QVkCommandBuffer::Command::CopyBufferToImage:
+ df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst,
+ cmd.args.copyBufferToImage.dstLayout,
+ cmd.args.copyBufferToImage.count,
+ cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex);
+ break;
+ case QVkCommandBuffer::Command::CopyImage:
+ df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout,
+ cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout,
+ 1, &cmd.args.copyImage.desc);
+ break;
+ case QVkCommandBuffer::Command::CopyImageToBuffer:
+ df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout,
+ cmd.args.copyImageToBuffer.dst,
+ 1, &cmd.args.copyImageToBuffer.desc);
+ break;
+ case QVkCommandBuffer::Command::ImageBarrier:
+ df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask,
+ 0, 0, nullptr, 0, nullptr,
+ 1, &cmd.args.imageBarrier.desc);
+ break;
+ case QVkCommandBuffer::Command::BufferBarrier:
+ df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask,
+ 0, 0, nullptr,
+ 1, &cmd.args.bufferBarrier.desc,
+ 0, nullptr);
+ break;
+ case QVkCommandBuffer::Command::BlitImage:
+ df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout,
+ cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout,
+ 1, &cmd.args.blitImage.desc,
+ cmd.args.blitImage.filter);
+ break;
+ case QVkCommandBuffer::Command::BeginRenderPass:
+ cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex;
+ df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc, VK_SUBPASS_CONTENTS_INLINE);
+ break;
+ case QVkCommandBuffer::Command::EndRenderPass:
+ df->vkCmdEndRenderPass(cbD->cb);
+ break;
+ case QVkCommandBuffer::Command::BindPipeline:
+ df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline);
+ break;
+ case QVkCommandBuffer::Command::BindDescriptorSet:
+ {
+ const uint32_t *offsets = nullptr;
+ if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0)
+ offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex;
+ df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint,
+ cmd.args.bindDescriptorSet.pipelineLayout,
+ 0, 1, &cmd.args.bindDescriptorSet.descSet,
+ cmd.args.bindDescriptorSet.dynamicOffsetCount,
+ offsets);
+ }
+ break;
+ case QVkCommandBuffer::Command::BindVertexBuffer:
+ df->vkCmdBindVertexBuffers(cbD->cb, cmd.args.bindVertexBuffer.startBinding,
+ cmd.args.bindVertexBuffer.count,
+ cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex,
+ cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex);
+ break;
+ case QVkCommandBuffer::Command::BindIndexBuffer:
+ df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf,
+ cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type);
+ break;
+ case QVkCommandBuffer::Command::SetViewport:
+ df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport);
+ break;
+ case QVkCommandBuffer::Command::SetScissor:
+ df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor);
+ break;
+ case QVkCommandBuffer::Command::SetBlendConstants:
+ df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c);
+ break;
+ case QVkCommandBuffer::Command::SetStencilRef:
+ df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref);
+ break;
+ case QVkCommandBuffer::Command::Draw:
+ df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
+ cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
+ break;
+ case QVkCommandBuffer::Command::DrawIndexed:
+ df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
+ cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
+ cmd.args.drawIndexed.firstInstance);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerBegin:
+ cmd.args.debugMarkerBegin.marker.pMarkerName =
+ cbD->pools.debugMarkerName[cmd.args.debugMarkerBegin.markerNameIndex].constData();
+ vkCmdDebugMarkerBegin(cbD->cb, &cmd.args.debugMarkerBegin.marker);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerEnd:
+ vkCmdDebugMarkerEnd(cbD->cb);
+ break;
+ case QVkCommandBuffer::Command::DebugMarkerInsert:
+ vkCmdDebugMarkerInsert(cbD->cb, &cmd.args.debugMarkerInsert.marker);
+ break;
+ case QVkCommandBuffer::Command::TransitionPassResources:
+ recordTransitionPassResources(cbD, cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]);
+ break;
+ case QVkCommandBuffer::Command::Dispatch:
+ df->vkCmdDispatch(cbD->cb, cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z);
+ break;
+ default:
+ break;
+ }
+ }
+
+ cbD->resetCommands();
+}
+
+static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::BufVertexInput:
+ return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
+ case QRhiPassResourceTracker::BufIndexRead:
+ return VK_ACCESS_INDEX_READ_BIT;
+ case QRhiPassResourceTracker::BufUniformRead:
+ return VK_ACCESS_UNIFORM_READ_BIT;
+ case QRhiPassResourceTracker::BufStorageLoad:
+ return VK_ACCESS_SHADER_READ_BIT;
+ case QRhiPassResourceTracker::BufStorageStore:
+ return VK_ACCESS_SHADER_WRITE_BIT;
+ case QRhiPassResourceTracker::BufStorageLoadStore:
+ return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage)
+{
+ switch (stage) {
+ case QRhiPassResourceTracker::BufVertexInputStage:
+ return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
+ case QRhiPassResourceTracker::BufVertexStage:
+ return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
+ case QRhiPassResourceTracker::BufFragmentStage:
+ return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ case QRhiPassResourceTracker::BufComputeStage:
+ return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage)
+{
+ QVkBuffer::UsageState u;
+ u.access = usage.access;
+ u.stage = usage.stage;
+ return u;
+}
+
+static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::TexSample:
+ return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ case QRhiPassResourceTracker::TexColorOutput:
+ return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ case QRhiPassResourceTracker::TexDepthOutput:
+ return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ case QRhiPassResourceTracker::TexStorageLoad:
+ Q_FALLTHROUGH();
+ case QRhiPassResourceTracker::TexStorageStore:
+ Q_FALLTHROUGH();
+ case QRhiPassResourceTracker::TexStorageLoadStore:
+ return VK_IMAGE_LAYOUT_GENERAL;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return VK_IMAGE_LAYOUT_GENERAL;
+}
+
+static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access)
+{
+ switch (access) {
+ case QRhiPassResourceTracker::TexSample:
+ return VK_ACCESS_SHADER_READ_BIT;
+ case QRhiPassResourceTracker::TexColorOutput:
+ return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ case QRhiPassResourceTracker::TexDepthOutput:
+ return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+ case QRhiPassResourceTracker::TexStorageLoad:
+ return VK_ACCESS_SHADER_READ_BIT;
+ case QRhiPassResourceTracker::TexStorageStore:
+ return VK_ACCESS_SHADER_WRITE_BIT;
+ case QRhiPassResourceTracker::TexStorageLoadStore:
+ return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage)
+{
+ switch (stage) {
+ case QRhiPassResourceTracker::TexVertexStage:
+ return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
+ case QRhiPassResourceTracker::TexFragmentStage:
+ return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ case QRhiPassResourceTracker::TexColorOutputStage:
+ return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ case QRhiPassResourceTracker::TexDepthOutputStage:
+ return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+ case QRhiPassResourceTracker::TexComputeStage:
+ return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ return 0;
+}
+
+static inline QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage)
+{
+ QVkTexture::UsageState u;
+ u.layout = VkImageLayout(usage.layout);
+ u.access = usage.access;
+ u.stage = usage.stage;
+ return u;
+}
+
+void QRhiVulkan::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
+ QVkBuffer *bufD,
+ int slot,
+ QRhiPassResourceTracker::BufferAccess access,
+ QRhiPassResourceTracker::BufferStage stage)
+{
+ QVkBuffer::UsageState &u(bufD->usageState[slot]);
+ passResTracker->registerBuffer(bufD, slot, &access, &stage, toPassTrackerUsageState(u));
+ u.access = toVkAccess(access);
+ u.stage = toVkPipelineStage(stage);
+}
+
+void QRhiVulkan::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
+ QVkTexture *texD,
+ QRhiPassResourceTracker::TextureAccess access,
+ QRhiPassResourceTracker::TextureStage stage)
+{
+ QVkTexture::UsageState &u(texD->usageState);
+ passResTracker->registerTexture(texD, &access, &stage, toPassTrackerUsageState(u));
+ u.layout = toVkLayout(access);
+ u.access = toVkAccess(access);
+ u.stage = toVkPipelineStage(stage);
+}
+
+void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker)
+{
+ if (tracker.isEmpty())
+ return;
+
+ const QVector<QRhiPassResourceTracker::Buffer> *buffers = tracker.buffers();
+ for (const QRhiPassResourceTracker::Buffer &b : *buffers) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b.buf);
+ VkAccessFlags access = toVkAccess(b.access);
+ VkPipelineStageFlags stage = toVkPipelineStage(b.stage);
+ QVkBuffer::UsageState s = toVkBufferUsageState(b.stateAtPassBegin);
+ if (!s.stage)
+ continue;
+ if (s.access == access && s.stage == stage) {
+ if (!accessIsWrite(access))
+ continue;
+ }
+ VkBufferMemoryBarrier bufMemBarrier;
+ memset(&bufMemBarrier, 0, sizeof(bufMemBarrier));
+ bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+ bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ bufMemBarrier.srcAccessMask = s.access;
+ bufMemBarrier.dstAccessMask = access;
+ bufMemBarrier.buffer = bufD->buffers[b.slot];
+ bufMemBarrier.size = VK_WHOLE_SIZE;
+ df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0,
+ 0, nullptr,
+ 1, &bufMemBarrier,
+ 0, nullptr);
+ }
+
+ const QVector<QRhiPassResourceTracker::Texture> *textures = tracker.textures();
+ for (const QRhiPassResourceTracker::Texture &t : *textures) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, t.tex);
+ VkImageLayout layout = toVkLayout(t.access);
+ VkAccessFlags access = toVkAccess(t.access);
+ VkPipelineStageFlags stage = toVkPipelineStage(t.stage);
+ QVkTexture::UsageState s = toVkTextureUsageState(t.stateAtPassBegin);
+ if (s.access == access && s.stage == stage && s.layout == layout) {
+ if (!accessIsWrite(access))
+ continue;
+ }
+ VkImageMemoryBarrier barrier;
+ memset(&barrier, 0, sizeof(barrier));
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.subresourceRange.aspectMask = !isDepthTextureFormat(texD->m_format)
+ ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
+ barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED
+ barrier.newLayout = layout;
+ barrier.srcAccessMask = s.access; // may be 0 but that's fine
+ barrier.dstAccessMask = access;
+ barrier.image = texD->image;
+ VkPipelineStageFlags srcStage = s.stage;
+ // stage mask cannot be 0
+ if (!srcStage)
+ srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0,
+ 0, nullptr,
+ 0, nullptr,
+ 1, &barrier);
+ }
+}
+
+QRhiSwapChain *QRhiVulkan::createSwapChain()
+{
+ return new QVkSwapChain(this);
+}
+
+QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size)
+{
+ return new QVkBuffer(this, type, usage, size);
+}
+
+int QRhiVulkan::ubufAlignment() const
+{
+ return ubufAlign; // typically 256 (bytes)
+}
+
+bool QRhiVulkan::isYUpInFramebuffer() const
+{
+ return false;
+}
+
+bool QRhiVulkan::isYUpInNDC() const
+{
+ return false;
+}
+
+bool QRhiVulkan::isClipDepthZeroToOne() const
+{
+ return true;
+}
+
+QMatrix4x4 QRhiVulkan::clipSpaceCorrMatrix() const
+{
+ // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/
+
+ static QMatrix4x4 m;
+ if (m.isIdentity()) {
+ // NB the ctor takes row-major
+ m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.5f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ }
+ return m;
+}
+
+bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+ // Note that with some SDKs the validation layer gives an odd warning about
+ // BC not being supported, even when our check here succeeds. Not much we
+ // can do about that.
+ if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) {
+ if (!physDevFeatures.textureCompressionBC)
+ return false;
+ }
+
+ if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) {
+ if (!physDevFeatures.textureCompressionETC2)
+ return false;
+ }
+
+ if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) {
+ if (!physDevFeatures.textureCompressionASTC_LDR)
+ return false;
+ }
+
+ VkFormat vkformat = toVkTextureFormat(format, flags);
+ VkFormatProperties props;
+ f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props);
+ return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
+}
+
+bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
+{
+ switch (feature) {
+ case QRhi::MultisampleTexture:
+ return true;
+ case QRhi::MultisampleRenderBuffer:
+ return true;
+ case QRhi::DebugMarkers:
+ return debugMarkersAvailable;
+ case QRhi::Timestamps:
+ return timestampValidBits != 0;
+ case QRhi::Instancing:
+ return true;
+ case QRhi::CustomInstanceStepRate:
+ return vertexAttribDivisorAvailable;
+ case QRhi::PrimitiveRestart:
+ return true;
+ case QRhi::NonDynamicUniformBuffers:
+ return true;
+ case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+ return true;
+ case QRhi::NPOTTextureRepeat:
+ return true;
+ case QRhi::RedOrAlpha8IsRed:
+ return true;
+ case QRhi::ElementIndexUint:
+ return true;
+ case QRhi::Compute:
+ return hasCompute;
+ case QRhi::WideLines:
+ return hasWideLines;
+ case QRhi::VertexShaderPointSize:
+ return true;
+ case QRhi::BaseVertex:
+ return true;
+ case QRhi::BaseInstance:
+ return true;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const
+{
+ switch (limit) {
+ case QRhi::TextureSizeMin:
+ return 1;
+ case QRhi::TextureSizeMax:
+ return physDevProperties.limits.maxImageDimension2D;
+ case QRhi::MaxColorAttachments:
+ return physDevProperties.limits.maxColorAttachments;
+ case QRhi::FramesInFlight:
+ return QVK_FRAMES_IN_FLIGHT;
+ default:
+ Q_UNREACHABLE();
+ return 0;
+ }
+}
+
+const QRhiNativeHandles *QRhiVulkan::nativeHandles()
+{
+ return &nativeHandlesStruct;
+}
+
+void QRhiVulkan::sendVMemStatsToProfiler()
+{
+ QRhiProfilerPrivate *rhiP = profilerPrivateOrNull();
+ if (!rhiP)
+ return;
+
+ VmaStats stats;
+ vmaCalculateStats(toVmaAllocator(allocator), &stats);
+ QRHI_PROF_F(vmemStat(stats.total.blockCount, stats.total.allocationCount,
+ stats.total.usedBytes, stats.total.unusedBytes));
+}
+
+void QRhiVulkan::makeThreadLocalNativeContextCurrent()
+{
+ // nothing to do here
+}
+
+QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+ int sampleCount, QRhiRenderBuffer::Flags flags)
+{
+ return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags);
+}
+
+QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize &pixelSize,
+ int sampleCount, QRhiTexture::Flags flags)
+{
+ return new QVkTexture(this, format, pixelSize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler::AddressMode u, QRhiSampler::AddressMode v)
+{
+ return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v);
+}
+
+QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags)
+{
+ return new QVkTextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiVulkan::createGraphicsPipeline()
+{
+ return new QVkGraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiVulkan::createComputePipeline()
+{
+ return new QVkComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings()
+{
+ return new QVkShaderResourceBindings(this);
+}
+
+void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+ QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps);
+ Q_ASSERT(psD->pipeline);
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) {
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindPipeline;
+ cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ cmd.args.bindPipeline.pipeline = psD->pipeline;
+ cbD->commands.append(cmd);
+
+ cbD->currentGraphicsPipeline = ps;
+ cbD->currentComputePipeline = nullptr;
+ cbD->currentPipelineGeneration = psD->generation;
+ }
+
+ psD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+QRhiPassResourceTracker::BufferStage toPassTrackerBufferStage(QRhiShaderResourceBinding::StageFlags stages)
+{
+ // pick the earlier stage (as this is going to be dstAccessMask)
+ if (stages.testFlag(QRhiShaderResourceBinding::VertexStage))
+ return QRhiPassResourceTracker::BufVertexStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ return QRhiPassResourceTracker::BufFragmentStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::ComputeStage))
+ return QRhiPassResourceTracker::BufComputeStage;
+
+ Q_UNREACHABLE();
+ return QRhiPassResourceTracker::BufVertexStage;
+}
+
+QRhiPassResourceTracker::TextureStage toPassTrackerTextureStage(QRhiShaderResourceBinding::StageFlags stages)
+{
+ // pick the earlier stage (as this is going to be dstAccessMask)
+ if (stages.testFlag(QRhiShaderResourceBinding::VertexStage))
+ return QRhiPassResourceTracker::TexVertexStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ return QRhiPassResourceTracker::TexFragmentStage;
+ if (stages.testFlag(QRhiShaderResourceBinding::ComputeStage))
+ return QRhiPassResourceTracker::TexComputeStage;
+
+ Q_UNREACHABLE();
+ return QRhiPassResourceTracker::TexVertexStage;
+}
+
+void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass != QVkCommandBuffer::NoPass);
+ QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline);
+ QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline);
+
+ if (!srb) {
+ if (gfxPsD)
+ srb = gfxPsD->m_shaderResourceBindings;
+ else
+ srb = compPsD->m_shaderResourceBindings;
+ }
+
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
+ bool hasSlottedResourceInSrb = false;
+ bool hasDynamicOffsetInSrb = false;
+
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->m_type == QRhiBuffer::Dynamic)
+ hasSlottedResourceInSrb = true;
+ if (b->u.ubuf.hasDynamicOffset)
+ hasDynamicOffsetInSrb = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ const int descSetIdx = hasSlottedResourceInSrb ? currentFrameSlot : 0;
+ bool rewriteDescSet = false;
+
+ // Do host writes and mark referenced shader resources as in-use.
+ // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects.
+ for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&srbD->sortedBindings[i]);
+ QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[descSetIdx][i]);
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
+ QRhiPassResourceTracker::BufUniformRead,
+ toPassTrackerBufferStage(b->stage));
+
+ // Check both the "local" id (the generation counter) and the
+ // global id. The latter is relevant when a newly allocated
+ // QRhiResource ends up with the same pointer as a previous one.
+ // (and that previous one could have been in an srb...)
+ if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+ rewriteDescSet = true;
+ bd.ubuf.id = bufD->m_id;
+ bd.ubuf.generation = bufD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::SampledTexture:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.stex.tex);
+ QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.sampler);
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ samplerD->lastActiveFrameSlot = currentFrameSlot;
+ trackedRegisterTexture(&passResTracker, texD,
+ QRhiPassResourceTracker::TexSample,
+ toPassTrackerTextureStage(b->stage));
+
+ if (texD->generation != bd.stex.texGeneration
+ || texD->m_id != bd.stex.texId
+ || samplerD->generation != bd.stex.samplerGeneration
+ || samplerD->m_id != bd.stex.samplerId)
+ {
+ rewriteDescSet = true;
+ bd.stex.texId = texD->m_id;
+ bd.stex.texGeneration = texD->generation;
+ bd.stex.samplerId = samplerD->m_id;
+ bd.stex.samplerGeneration = samplerD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ {
+ QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
+ Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore));
+ texD->lastActiveFrameSlot = currentFrameSlot;
+ QRhiPassResourceTracker::TextureAccess access;
+ if (b->type == QRhiShaderResourceBinding::ImageLoad)
+ access = QRhiPassResourceTracker::TexStorageLoad;
+ else if (b->type == QRhiShaderResourceBinding::ImageStore)
+ access = QRhiPassResourceTracker::TexStorageStore;
+ else
+ access = QRhiPassResourceTracker::TexStorageLoadStore;
+ trackedRegisterTexture(&passResTracker, texD,
+ access,
+ toPassTrackerTextureStage(b->stage));
+
+ if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
+ rewriteDescSet = true;
+ bd.simage.id = texD->m_id;
+ bd.simage.generation = texD->generation;
+ }
+ }
+ break;
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
+
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ QRhiPassResourceTracker::BufferAccess access;
+ if (b->type == QRhiShaderResourceBinding::BufferLoad)
+ access = QRhiPassResourceTracker::BufStorageLoad;
+ else if (b->type == QRhiShaderResourceBinding::BufferStore)
+ access = QRhiPassResourceTracker::BufStorageStore;
+ else
+ access = QRhiPassResourceTracker::BufStorageLoadStore;
+ trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
+ access,
+ toPassTrackerBufferStage(b->stage));
+
+ if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
+ rewriteDescSet = true;
+ bd.sbuf.id = bufD->m_id;
+ bd.sbuf.generation = bufD->generation;
+ }
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+
+ // write descriptor sets, if needed
+ if (rewriteDescSet)
+ updateShaderResourceBindings(srb, descSetIdx);
+
+ // make sure the descriptors for the correct slot will get bound.
+ // also, dynamic offsets always need a bind.
+ const bool forceRebind = (hasSlottedResourceInSrb && cbD->currentDescSetSlot != descSetIdx) || hasDynamicOffsetInSrb;
+
+ const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+
+ if (forceRebind || rewriteDescSet || srbChanged || cbD->currentSrbGeneration != srbD->generation) {
+ QVarLengthArray<uint32_t, 4> dynOfs;
+ if (hasDynamicOffsetInSrb) {
+ // Filling out dynOfs based on the sorted bindings is important
+ // because dynOfs has to be ordered based on the binding numbers,
+ // and neither srb nor dynamicOffsets has any such ordering
+ // requirement.
+ for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) {
+ uint32_t offset = 0;
+ for (int i = 0; i < dynamicOffsetCount; ++i) {
+ const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+ if (dynOfs.first == b->binding) {
+ offset = dynOfs.second;
+ break;
+ }
+ }
+ dynOfs.append(offset); // use 0 if dynamicOffsets did not contain this binding
+ }
+ }
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet;
+ cmd.args.bindDescriptorSet.bindPoint = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS
+ : VK_PIPELINE_BIND_POINT_COMPUTE;
+ cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout;
+ cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx];
+ cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.count();
+ cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.count();
+ cbD->pools.dynamicOffset.append(dynOfs.constData(), dynOfs.count());
+ cbD->commands.append(cmd);
+
+ if (gfxPsD) {
+ cbD->currentGraphicsSrb = srb;
+ cbD->currentComputeSrb = nullptr;
+ } else {
+ cbD->currentGraphicsSrb = nullptr;
+ cbD->currentComputeSrb = srb;
+ }
+ cbD->currentSrbGeneration = srbD->generation;
+ cbD->currentDescSetSlot = descSetIdx;
+ }
+
+ srbD->lastActiveFrameSlot = currentFrameSlot;
+}
+
+void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+ QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]);
+
+ bool needsBindVBuf = false;
+ for (int i = 0; i < bindingCount; ++i) {
+ const int inputSlot = startBinding + i;
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
+ Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+ bufD->lastActiveFrameSlot = currentFrameSlot;
+ if (bufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(bufD);
+
+ const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
+ if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf
+ || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+ {
+ needsBindVBuf = true;
+ cbD->currentVertexBuffers[inputSlot] = vkvertexbuf;
+ cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+ }
+ }
+
+ if (needsBindVBuf) {
+ QVarLengthArray<VkBuffer, 4> bufs;
+ QVarLengthArray<VkDeviceSize, 4> ofs;
+ for (int i = 0; i < bindingCount; ++i) {
+ QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first);
+ const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
+ bufs.append(bufD->buffers[slot]);
+ ofs.append(bindings[i].second);
+ trackedRegisterBuffer(&passResTracker, bufD, slot,
+ QRhiPassResourceTracker::BufVertexInput,
+ QRhiPassResourceTracker::BufVertexInputStage);
+ }
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer;
+ cmd.args.bindVertexBuffer.startBinding = startBinding;
+ cmd.args.bindVertexBuffer.count = bufs.count();
+ cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.count();
+ cbD->pools.vertexBuffer.append(bufs.constData(), bufs.count());
+ cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.count();
+ cbD->pools.vertexBufferOffset.append(ofs.constData(), ofs.count());
+ cbD->commands.append(cmd);
+ }
+
+ if (indexBuf) {
+ QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf);
+ Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+ ibufD->lastActiveFrameSlot = currentFrameSlot;
+ if (ibufD->m_type == QRhiBuffer::Dynamic)
+ executeBufferHostWritesForCurrentFrame(ibufD);
+
+ const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0;
+ const VkBuffer vkindexbuf = ibufD->buffers[slot];
+ const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16
+ : VK_INDEX_TYPE_UINT32;
+
+ if (cbD->currentIndexBuffer != vkindexbuf
+ || cbD->currentIndexOffset != indexOffset
+ || cbD->currentIndexFormat != type)
+ {
+ cbD->currentIndexBuffer = vkindexbuf;
+ cbD->currentIndexOffset = indexOffset;
+ cbD->currentIndexFormat = type;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::BindIndexBuffer;
+ cmd.args.bindIndexBuffer.buf = vkindexbuf;
+ cmd.args.bindIndexBuffer.ofs = indexOffset;
+ cmd.args.bindIndexBuffer.type = type;
+ cbD->commands.append(cmd);
+
+ trackedRegisterBuffer(&passResTracker, ibufD, slot,
+ QRhiPassResourceTracker::BufIndexRead,
+ QRhiPassResourceTracker::BufVertexInputStage);
+ }
+ }
+}
+
+void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in VkViewport but bottom-left in QRhiViewport
+ float x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, viewport.viewport(), &x, &y, &w, &h))
+ return;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetViewport;
+ VkViewport *vp = &cmd.args.setViewport.viewport;
+ vp->x = x;
+ vp->y = y;
+ vp->width = w;
+ vp->height = h;
+ vp->minDepth = viewport.minDepth();
+ vp->maxDepth = viewport.maxDepth();
+ cbD->commands.append(cmd);
+
+ if (!QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)) {
+ cmd.cmd = QVkCommandBuffer::Command::SetScissor;
+ VkRect2D *s = &cmd.args.setScissor.scissor;
+ s->offset.x = x;
+ s->offset.y = y;
+ s->extent.width = w;
+ s->extent.height = h;
+ cbD->commands.append(cmd);
+ }
+}
+
+void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+ Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor));
+ const QSize outputSize = cbD->currentTarget->pixelSize();
+
+ // x,y is top-left in VkRect2D but bottom-left in QRhiScissor
+ int x, y, w, h;
+ if (!qrhi_toTopLeftRenderTargetRect(outputSize, scissor.scissor(), &x, &y, &w, &h))
+ return;
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetScissor;
+ VkRect2D *s = &cmd.args.setScissor.scissor;
+ s->offset.x = x;
+ s->offset.y = y;
+ s->extent.width = w;
+ s->extent.height = h;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetBlendConstants;
+ cmd.args.setBlendConstants.c[0] = c.redF();
+ cmd.args.setBlendConstants.c[1] = c.greenF();
+ cmd.args.setBlendConstants.c[2] = c.blueF();
+ cmd.args.setBlendConstants.c[3] = c.alphaF();
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::SetStencilRef;
+ cmd.args.setStencilRef.ref = refValue;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::Draw;
+ cmd.args.draw.vertexCount = vertexCount;
+ cmd.args.draw.instanceCount = instanceCount;
+ cmd.args.draw.firstVertex = firstVertex;
+ cmd.args.draw.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass);
+
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DrawIndexed;
+ cmd.args.drawIndexed.indexCount = indexCount;
+ cmd.args.drawIndexed.instanceCount = instanceCount;
+ cmd.args.drawIndexed.firstIndex = firstIndex;
+ cmd.args.drawIndexed.vertexOffset = vertexOffset;
+ cmd.args.drawIndexed.firstInstance = firstInstance;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ VkDebugMarkerMarkerInfoEXT marker;
+ memset(&marker, 0, sizeof(marker));
+ marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin;
+ cmd.args.debugMarkerBegin.marker = marker;
+ cmd.args.debugMarkerBegin.markerNameIndex = cbD->pools.debugMarkerName.count();
+ cbD->pools.debugMarkerName.append(name);
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd;
+ cbD->commands.append(cmd);
+}
+
+void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+ if (!debugMarkers || !debugMarkersAvailable)
+ return;
+
+ VkDebugMarkerMarkerInfoEXT marker;
+ memset(&marker, 0, sizeof(marker));
+ marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
+ marker.pMarkerName = msg.constData();
+
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ QVkCommandBuffer::Command cmd;
+ cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert;
+ cmd.args.debugMarkerInsert.marker = marker;
+ cbD->commands.append(cmd);
+}
+
+const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb)
+{
+ return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+void QRhiVulkan::endExternal(QRhiCommandBuffer *cb)
+{
+ QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb);
+ cbD->resetCachedState();
+}
+
+void QRhiVulkan::setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot)
+{
+ if (!debugMarkers || !debugMarkersAvailable || name.isEmpty())
+ return;
+
+ VkDebugMarkerObjectNameInfoEXT nameInfo;
+ memset(&nameInfo, 0, sizeof(nameInfo));
+ nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
+ nameInfo.objectType = type;
+ nameInfo.object = object;
+ QByteArray decoratedName = name;
+ if (slot >= 0) {
+ decoratedName += '/';
+ decoratedName += QByteArray::number(slot);
+ }
+ nameInfo.pObjectName = decoratedName.constData();
+ vkDebugMarkerSetObjectName(dev, &nameInfo);
+}
+
+static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage)
+{
+ int u = 0;
+ if (usage.testFlag(QRhiBuffer::VertexBuffer))
+ u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+ if (usage.testFlag(QRhiBuffer::IndexBuffer))
+ u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+ if (usage.testFlag(QRhiBuffer::UniformBuffer))
+ u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ if (usage.testFlag(QRhiBuffer::StorageBuffer))
+ u |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+ return VkBufferUsageFlagBits(u);
+}
+
+static inline VkFilter toVkFilter(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::Nearest:
+ return VK_FILTER_NEAREST;
+ case QRhiSampler::Linear:
+ return VK_FILTER_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return VK_FILTER_NEAREST;
+ }
+}
+
+static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f)
+{
+ switch (f) {
+ case QRhiSampler::None:
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ case QRhiSampler::Nearest:
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ case QRhiSampler::Linear:
+ return VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ default:
+ Q_UNREACHABLE();
+ return VK_SAMPLER_MIPMAP_MODE_NEAREST;
+ }
+}
+
+static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m)
+{
+ switch (m) {
+ case QRhiSampler::Repeat:
+ return VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ case QRhiSampler::ClampToEdge:
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ case QRhiSampler::Border:
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
+ case QRhiSampler::Mirror:
+ return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+ case QRhiSampler::MirrorOnce:
+ return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
+ default:
+ Q_UNREACHABLE();
+ return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ }
+}
+
+static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type)
+{
+ switch (type) {
+ case QRhiShaderStage::Vertex:
+ return VK_SHADER_STAGE_VERTEX_BIT;
+ case QRhiShaderStage::Fragment:
+ return VK_SHADER_STAGE_FRAGMENT_BIT;
+ case QRhiShaderStage::Compute:
+ return VK_SHADER_STAGE_COMPUTE_BIT;
+ default:
+ Q_UNREACHABLE();
+ return VK_SHADER_STAGE_VERTEX_BIT;
+ }
+}
+
+static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+ switch (format) {
+ case QRhiVertexInputAttribute::Float4:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ case QRhiVertexInputAttribute::Float3:
+ return VK_FORMAT_R32G32B32_SFLOAT;
+ case QRhiVertexInputAttribute::Float2:
+ return VK_FORMAT_R32G32_SFLOAT;
+ case QRhiVertexInputAttribute::Float:
+ return VK_FORMAT_R32_SFLOAT;
+ case QRhiVertexInputAttribute::UNormByte4:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte2:
+ return VK_FORMAT_R8G8_UNORM;
+ case QRhiVertexInputAttribute::UNormByte:
+ return VK_FORMAT_R8_UNORM;
+ default:
+ Q_UNREACHABLE();
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ }
+}
+
+static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ case QRhiGraphicsPipeline::TriangleStrip:
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
+ case QRhiGraphicsPipeline::Lines:
+ return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
+ case QRhiGraphicsPipeline::LineStrip:
+ return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
+ case QRhiGraphicsPipeline::Points:
+ return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+ default:
+ Q_UNREACHABLE();
+ return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ }
+}
+
+static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+ switch (c) {
+ case QRhiGraphicsPipeline::None:
+ return VK_CULL_MODE_NONE;
+ case QRhiGraphicsPipeline::Front:
+ return VK_CULL_MODE_FRONT_BIT;
+ case QRhiGraphicsPipeline::Back:
+ return VK_CULL_MODE_BACK_BIT;
+ default:
+ Q_UNREACHABLE();
+ return VK_CULL_MODE_NONE;
+ }
+}
+
+static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::CCW:
+ return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ case QRhiGraphicsPipeline::CW:
+ return VK_FRONT_FACE_CLOCKWISE;
+ default:
+ Q_UNREACHABLE();
+ return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ }
+}
+
+static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c)
+{
+ int f = 0;
+ if (c.testFlag(QRhiGraphicsPipeline::R))
+ f |= VK_COLOR_COMPONENT_R_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::G))
+ f |= VK_COLOR_COMPONENT_G_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::B))
+ f |= VK_COLOR_COMPONENT_B_BIT;
+ if (c.testFlag(QRhiGraphicsPipeline::A))
+ f |= VK_COLOR_COMPONENT_A_BIT;
+ return VkColorComponentFlags(f);
+}
+
+static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f)
+{
+ switch (f) {
+ case QRhiGraphicsPipeline::Zero:
+ return VK_BLEND_FACTOR_ZERO;
+ case QRhiGraphicsPipeline::One:
+ return VK_BLEND_FACTOR_ONE;
+ case QRhiGraphicsPipeline::SrcColor:
+ return VK_BLEND_FACTOR_SRC_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrcColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+ case QRhiGraphicsPipeline::DstColor:
+ return VK_BLEND_FACTOR_DST_COLOR;
+ case QRhiGraphicsPipeline::OneMinusDstColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
+ case QRhiGraphicsPipeline::SrcAlpha:
+ return VK_BLEND_FACTOR_SRC_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ case QRhiGraphicsPipeline::DstAlpha:
+ return VK_BLEND_FACTOR_DST_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusDstAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
+ case QRhiGraphicsPipeline::ConstantColor:
+ return VK_BLEND_FACTOR_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::OneMinusConstantColor:
+ return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
+ case QRhiGraphicsPipeline::ConstantAlpha:
+ return VK_BLEND_FACTOR_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
+ case QRhiGraphicsPipeline::SrcAlphaSaturate:
+ return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
+ case QRhiGraphicsPipeline::Src1Color:
+ return VK_BLEND_FACTOR_SRC1_COLOR;
+ case QRhiGraphicsPipeline::OneMinusSrc1Color:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
+ case QRhiGraphicsPipeline::Src1Alpha:
+ return VK_BLEND_FACTOR_SRC1_ALPHA;
+ case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+ return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
+ default:
+ Q_UNREACHABLE();
+ return VK_BLEND_FACTOR_ZERO;
+ }
+}
+
+static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Add:
+ return VK_BLEND_OP_ADD;
+ case QRhiGraphicsPipeline::Subtract:
+ return VK_BLEND_OP_SUBTRACT;
+ case QRhiGraphicsPipeline::ReverseSubtract:
+ return VK_BLEND_OP_REVERSE_SUBTRACT;
+ case QRhiGraphicsPipeline::Min:
+ return VK_BLEND_OP_MIN;
+ case QRhiGraphicsPipeline::Max:
+ return VK_BLEND_OP_MAX;
+ default:
+ Q_UNREACHABLE();
+ return VK_BLEND_OP_ADD;
+ }
+}
+
+static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::Never:
+ return VK_COMPARE_OP_NEVER;
+ case QRhiGraphicsPipeline::Less:
+ return VK_COMPARE_OP_LESS;
+ case QRhiGraphicsPipeline::Equal:
+ return VK_COMPARE_OP_EQUAL;
+ case QRhiGraphicsPipeline::LessOrEqual:
+ return VK_COMPARE_OP_LESS_OR_EQUAL;
+ case QRhiGraphicsPipeline::Greater:
+ return VK_COMPARE_OP_GREATER;
+ case QRhiGraphicsPipeline::NotEqual:
+ return VK_COMPARE_OP_NOT_EQUAL;
+ case QRhiGraphicsPipeline::GreaterOrEqual:
+ return VK_COMPARE_OP_GREATER_OR_EQUAL;
+ case QRhiGraphicsPipeline::Always:
+ return VK_COMPARE_OP_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return VK_COMPARE_OP_ALWAYS;
+ }
+}
+
+static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+ switch (op) {
+ case QRhiGraphicsPipeline::StencilZero:
+ return VK_STENCIL_OP_ZERO;
+ case QRhiGraphicsPipeline::Keep:
+ return VK_STENCIL_OP_KEEP;
+ case QRhiGraphicsPipeline::Replace:
+ return VK_STENCIL_OP_REPLACE;
+ case QRhiGraphicsPipeline::IncrementAndClamp:
+ return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
+ case QRhiGraphicsPipeline::DecrementAndClamp:
+ return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
+ case QRhiGraphicsPipeline::Invert:
+ return VK_STENCIL_OP_INVERT;
+ case QRhiGraphicsPipeline::IncrementAndWrap:
+ return VK_STENCIL_OP_INCREMENT_AND_WRAP;
+ case QRhiGraphicsPipeline::DecrementAndWrap:
+ return VK_STENCIL_OP_DECREMENT_AND_WRAP;
+ default:
+ Q_UNREACHABLE();
+ return VK_STENCIL_OP_KEEP;
+ }
+}
+
+static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src)
+{
+ dst->failOp = toVkStencilOp(src.failOp);
+ dst->passOp = toVkStencilOp(src.passOp);
+ dst->depthFailOp = toVkStencilOp(src.depthFailOp);
+ dst->compareOp = toVkCompareOp(src.compareOp);
+}
+
+static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBindingPrivate *b)
+{
+ switch (b->type) {
+ case QRhiShaderResourceBinding::UniformBuffer:
+ return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC
+ : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+
+ case QRhiShaderResourceBinding::SampledTexture:
+ return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+
+ case QRhiShaderResourceBinding::ImageLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::ImageLoadStore:
+ return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+
+ case QRhiShaderResourceBinding::BufferLoad:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferStore:
+ Q_FALLTHROUGH();
+ case QRhiShaderResourceBinding::BufferLoadStore:
+ return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+
+ default:
+ Q_UNREACHABLE();
+ return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ }
+}
+
+static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage)
+{
+ int s = 0;
+ if (stage.testFlag(QRhiShaderResourceBinding::VertexStage))
+ s |= VK_SHADER_STAGE_VERTEX_BIT;
+ if (stage.testFlag(QRhiShaderResourceBinding::FragmentStage))
+ s |= VK_SHADER_STAGE_FRAGMENT_BIT;
+ if (stage.testFlag(QRhiShaderResourceBinding::ComputeStage))
+ s |= VK_SHADER_STAGE_COMPUTE_BIT;
+ return VkShaderStageFlags(s);
+}
+
+static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op)
+{
+ switch (op) {
+ case QRhiSampler::Never:
+ return VK_COMPARE_OP_NEVER;
+ case QRhiSampler::Less:
+ return VK_COMPARE_OP_LESS;
+ case QRhiSampler::Equal:
+ return VK_COMPARE_OP_EQUAL;
+ case QRhiSampler::LessOrEqual:
+ return VK_COMPARE_OP_LESS_OR_EQUAL;
+ case QRhiSampler::Greater:
+ return VK_COMPARE_OP_GREATER;
+ case QRhiSampler::NotEqual:
+ return VK_COMPARE_OP_NOT_EQUAL;
+ case QRhiSampler::GreaterOrEqual:
+ return VK_COMPARE_OP_GREATER_OR_EQUAL;
+ case QRhiSampler::Always:
+ return VK_COMPARE_OP_ALWAYS;
+ default:
+ Q_UNREACHABLE();
+ return VK_COMPARE_OP_NEVER;
+ }
+}
+
+QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size)
+ : QRhiBuffer(rhi, type, usage, size)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE;
+ allocations[i] = stagingAllocations[i] = nullptr;
+ }
+}
+
+QVkBuffer::~QVkBuffer()
+{
+ release();
+}
+
+void QVkBuffer::release()
+{
+ if (!buffers[0])
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Buffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ e.buffer.buffers[i] = buffers[i];
+ e.buffer.allocations[i] = allocations[i];
+ e.buffer.stagingBuffers[i] = stagingBuffers[i];
+ e.buffer.stagingAllocations[i] = stagingAllocations[i];
+
+ buffers[i] = VK_NULL_HANDLE;
+ allocations[i] = nullptr;
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ pendingDynamicUpdates[i].clear();
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseBuffer(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkBuffer::build()
+{
+ if (buffers[0])
+ release();
+
+ if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+ qWarning("StorageBuffer cannot be combined with Dynamic");
+ return false;
+ }
+
+ const int nonZeroSize = m_size <= 0 ? 256 : m_size;
+
+ VkBufferCreateInfo bufferInfo;
+ memset(&bufferInfo, 0, sizeof(bufferInfo));
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = nonZeroSize;
+ bufferInfo.usage = toVkBufferUsage(m_usage);
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+
+ if (m_type == Dynamic) {
+#ifndef Q_OS_DARWIN // not for MoltenVK
+ // Keep mapped all the time. Essential f.ex. with some mobile GPUs,
+ // where mapping and unmapping an entire allocation every time updating
+ // a suballocated buffer presents a significant perf. hit.
+ allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
+#endif
+ // host visible, frequent changes
+ allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
+ } else {
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+ bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = VK_SUCCESS;
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ buffers[i] = VK_NULL_HANDLE;
+ allocations[i] = nullptr;
+ usageState[i].access = usageState[i].stage = 0;
+ if (i == 0 || m_type == Dynamic) {
+ VmaAllocation allocation;
+ err = vmaCreateBuffer(toVmaAllocator(rhiD->allocator), &bufferInfo, &allocInfo, &buffers[i], &allocation, nullptr);
+ if (err != VK_SUCCESS)
+ break;
+
+ allocations[i] = allocation;
+ if (m_type == Dynamic)
+ pendingDynamicUpdates[i].reserve(16);
+
+ rhiD->setObjectName(uint64_t(buffers[i]), VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, m_objectName,
+ m_type == Dynamic ? i : -1);
+ }
+ }
+
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create buffer: %d", err);
+ return false;
+ }
+
+ QRHI_PROF;
+ QRHI_PROF_F(newBuffer(this, nonZeroSize, m_type != Dynamic ? 1 : QVK_FRAMES_IN_FLIGHT, 0));
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags)
+{
+}
+
+QVkRenderBuffer::~QVkRenderBuffer()
+{
+ release();
+ delete backingTexture;
+}
+
+void QVkRenderBuffer::release()
+{
+ if (!memory && !backingTexture)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::RenderBuffer;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderBuffer.memory = memory;
+ e.renderBuffer.image = image;
+ e.renderBuffer.imageView = imageView;
+
+ memory = VK_NULL_HANDLE;
+ image = VK_NULL_HANDLE;
+ imageView = VK_NULL_HANDLE;
+
+ if (backingTexture) {
+ Q_ASSERT(backingTexture->lastActiveFrameSlot == -1);
+ backingTexture->lastActiveFrameSlot = e.lastActiveFrameSlot;
+ backingTexture->release();
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseRenderBuffer(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkRenderBuffer::build()
+{
+ if (memory || backingTexture)
+ release();
+
+ if (m_pixelSize.isEmpty())
+ return false;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QRHI_PROF;
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ switch (m_type) {
+ case QRhiRenderBuffer::Color:
+ {
+ if (!backingTexture) {
+ backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(QRhiTexture::RGBA8,
+ m_pixelSize,
+ m_sampleCount,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource));
+ } else {
+ backingTexture->setPixelSize(m_pixelSize);
+ backingTexture->setSampleCount(m_sampleCount);
+ }
+ backingTexture->setName(m_objectName);
+ if (!backingTexture->build())
+ return false;
+ vkformat = backingTexture->vkformat;
+ QRHI_PROF_F(newRenderBuffer(this, false, false, samples));
+ }
+ break;
+ case QRhiRenderBuffer::DepthStencil:
+ vkformat = rhiD->optimalDepthStencilFormat();
+ if (!rhiD->createTransientImage(vkformat,
+ m_pixelSize,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT,
+ samples,
+ &memory,
+ &image,
+ &imageView,
+ 1))
+ {
+ return false;
+ }
+ rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName);
+ QRHI_PROF_F(newRenderBuffer(this, true, false, samples));
+ break;
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ lastActiveFrameSlot = -1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QRhiTexture::Format QVkRenderBuffer::backingFormat() const
+{
+ return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags)
+ : QRhiTexture(rhi, format, pixelSize, sampleCount, flags)
+{
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ }
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i)
+ perLevelImageViews[i] = VK_NULL_HANDLE;
+}
+
+QVkTexture::~QVkTexture()
+{
+ release();
+}
+
+void QVkTexture::release()
+{
+ if (!image)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Texture;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.texture.image = owns ? image : VK_NULL_HANDLE;
+ e.texture.imageView = imageView;
+ e.texture.allocation = owns ? imageAlloc : nullptr;
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) {
+ e.texture.stagingBuffers[i] = stagingBuffers[i];
+ e.texture.stagingAllocations[i] = stagingAllocations[i];
+
+ stagingBuffers[i] = VK_NULL_HANDLE;
+ stagingAllocations[i] = nullptr;
+ }
+
+ for (int i = 0; i < QRhi::MAX_LEVELS; ++i) {
+ e.texture.extraImageViews[i] = perLevelImageViews[i];
+ perLevelImageViews[i] = VK_NULL_HANDLE;
+ }
+
+ image = VK_NULL_HANDLE;
+ imageView = VK_NULL_HANDLE;
+ imageAlloc = nullptr;
+ nativeHandlesStruct.image = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseTexture(this));
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkTexture::prepareBuild(QSize *adjustedSize)
+{
+ if (image)
+ release();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ vkformat = toVkTextureFormat(m_format, m_flags);
+ VkFormatProperties props;
+ rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props);
+ const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
+ if (!canSampleOptimal) {
+ qWarning("Texture sampling with optimal tiling for format %d not supported", vkformat);
+ return false;
+ }
+
+ const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize;
+ const bool isCube = m_flags.testFlag(CubeMap);
+ const bool hasMipMaps = m_flags.testFlag(MipMapped);
+
+ mipLevelCount = hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1;
+ const int maxLevels = QRhi::MAX_LEVELS;
+ if (mipLevelCount > maxLevels) {
+ qWarning("Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels);
+ mipLevelCount = maxLevels;
+ }
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+ if (samples > VK_SAMPLE_COUNT_1_BIT) {
+ if (isCube) {
+ qWarning("Cubemap texture cannot be multisample");
+ return false;
+ }
+ if (hasMipMaps) {
+ qWarning("Multisample texture cannot have mipmaps");
+ return false;
+ }
+ }
+
+ usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+ usageState.access = 0;
+ usageState.stage = 0;
+
+ if (adjustedSize)
+ *adjustedSize = size;
+
+ return true;
+}
+
+bool QVkTexture::finishBuild()
+{
+ QRHI_RES_RHI(QRhiVulkan);
+
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = image;
+ viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.levelCount = mipLevelCount;
+ viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
+
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image view: %d", err);
+ return false;
+ }
+
+ nativeHandlesStruct.image = image;
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+
+ return true;
+}
+
+bool QVkTexture::build()
+{
+ QSize size;
+ if (!prepareBuild(&size))
+ return false;
+
+ const bool isRenderTarget = m_flags.testFlag(QRhiTexture::RenderTarget);
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ VkImageCreateInfo imageInfo;
+ memset(&imageInfo, 0, sizeof(imageInfo));
+ imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imageInfo.flags = isCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0;
+ imageInfo.imageType = VK_IMAGE_TYPE_2D;
+ imageInfo.format = vkformat;
+ imageInfo.extent.width = size.width();
+ imageInfo.extent.height = size.height();
+ imageInfo.extent.depth = 1;
+ imageInfo.mipLevels = mipLevelCount;
+ imageInfo.arrayLayers = isCube ? 6 : 1;
+ imageInfo.samples = samples;
+ imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
+
+ imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+ if (isRenderTarget) {
+ if (isDepth)
+ imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ else
+ imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ }
+ if (m_flags.testFlag(QRhiTexture::UsedAsTransferSource))
+ imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (m_flags.testFlag(QRhiTexture::UsedWithGenerateMips))
+ imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ if (m_flags.testFlag(QRhiTexture::UsedWithLoadStore))
+ imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT;
+
+ VmaAllocationCreateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VmaAllocation allocation;
+ VkResult err = vmaCreateImage(toVmaAllocator(rhiD->allocator), &imageInfo, &allocInfo, &image, &allocation, nullptr);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image: %d", err);
+ return false;
+ }
+ imageAlloc = allocation;
+
+ if (!finishBuild())
+ return false;
+
+ rhiD->setObjectName(uint64_t(image), VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, m_objectName);
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, true, mipLevelCount, isCube ? 6 : 1, samples));
+
+ owns = true;
+ rhiD->registerResource(this);
+ return true;
+}
+
+bool QVkTexture::buildFrom(const QRhiNativeHandles *src)
+{
+ const QRhiVulkanTextureNativeHandles *h = static_cast<const QRhiVulkanTextureNativeHandles *>(src);
+ if (!h || !h->image)
+ return false;
+
+ if (!prepareBuild())
+ return false;
+
+ image = h->image;
+
+ if (!finishBuild())
+ return false;
+
+ QRHI_PROF;
+ QRHI_PROF_F(newTexture(this, false, mipLevelCount, m_flags.testFlag(CubeMap) ? 6 : 1, samples));
+
+ usageState.layout = h->layout;
+
+ owns = false;
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->registerResource(this);
+ return true;
+}
+
+const QRhiNativeHandles *QVkTexture::nativeHandles()
+{
+ nativeHandlesStruct.layout = usageState.layout;
+ return &nativeHandlesStruct;
+}
+
+VkImageView QVkTexture::imageViewForLevel(int level)
+{
+ Q_ASSERT(level >= 0 && level < int(mipLevelCount));
+ if (perLevelImageViews[level] != VK_NULL_HANDLE)
+ return perLevelImageViews[level];
+
+ const bool isDepth = isDepthTextureFormat(m_format);
+ const bool isCube = m_flags.testFlag(CubeMap);
+
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = image;
+ viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = level;
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = 0;
+ viewInfo.subresourceRange.layerCount = isCube ? 6 : 1;
+
+ VkImageView v = VK_NULL_HANDLE;
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create image view: %d", err);
+ return VK_NULL_HANDLE;
+ }
+
+ perLevelImageViews[level] = v;
+ return v;
+}
+
+QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v)
+ : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v)
+{
+}
+
+QVkSampler::~QVkSampler()
+{
+ release();
+}
+
+void QVkSampler::release()
+{
+ if (!sampler)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Sampler;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.sampler.sampler = sampler;
+ sampler = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+ rhiD->unregisterResource(this);
+}
+
+bool QVkSampler::build()
+{
+ if (sampler)
+ release();
+
+ VkSamplerCreateInfo samplerInfo;
+ memset(&samplerInfo, 0, sizeof(samplerInfo));
+ samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+ samplerInfo.magFilter = toVkFilter(m_magFilter);
+ samplerInfo.minFilter = toVkFilter(m_minFilter);
+ samplerInfo.mipmapMode = toVkMipmapMode(m_mipmapMode);
+ samplerInfo.addressModeU = toVkAddressMode(m_addressU);
+ samplerInfo.addressModeV = toVkAddressMode(m_addressV);
+ samplerInfo.addressModeW = toVkAddressMode(m_addressW);
+ samplerInfo.maxAnisotropy = 1.0f;
+ samplerInfo.compareEnable = m_compareOp != Never;
+ samplerInfo.compareOp = toVkTextureCompareOp(m_compareOp);
+ samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create sampler: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi)
+ : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QVkRenderPassDescriptor::~QVkRenderPassDescriptor()
+{
+ release();
+}
+
+void QVkRenderPassDescriptor::release()
+{
+ if (!rp)
+ return;
+
+ if (!ownsRp) {
+ rp = VK_NULL_HANDLE;
+ return;
+ }
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::RenderPass;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.renderPass.rp = rp;
+
+ rp = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QVkReferenceRenderTarget::QVkReferenceRenderTarget(QRhiImplementation *rhi)
+ : QRhiRenderTarget(rhi)
+{
+}
+
+QVkReferenceRenderTarget::~QVkReferenceRenderTarget()
+{
+ release();
+}
+
+void QVkReferenceRenderTarget::release()
+{
+ // nothing to do here
+}
+
+QSize QVkReferenceRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QVkReferenceRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QVkReferenceRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QVkTextureRenderTarget::QVkTextureRenderTarget(QRhiImplementation *rhi,
+ const QRhiTextureRenderTargetDescription &desc,
+ Flags flags)
+ : QRhiTextureRenderTarget(rhi, desc, flags)
+{
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ rtv[att] = VK_NULL_HANDLE;
+ resrtv[att] = VK_NULL_HANDLE;
+ }
+}
+
+QVkTextureRenderTarget::~QVkTextureRenderTarget()
+{
+ release();
+}
+
+void QVkTextureRenderTarget::release()
+{
+ if (!d.fb)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.textureRenderTarget.fb = d.fb;
+ d.fb = VK_NULL_HANDLE;
+
+ for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) {
+ e.textureRenderTarget.rtv[att] = rtv[att];
+ e.textureRenderTarget.resrtv[att] = resrtv[att];
+ rtv[att] = VK_NULL_HANDLE;
+ resrtv[att] = VK_NULL_HANDLE;
+ }
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+ // not yet built so cannot rely on data computed in build()
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
+ if (!rhiD->createOffscreenRenderPass(&rp->rp,
+ m_desc.colorAttachments(),
+ m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
+ m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents),
+ m_desc.depthStencilBuffer(),
+ m_desc.depthTexture()))
+ {
+ delete rp;
+ return nullptr;
+ }
+
+ rp->ownsRp = true;
+ rhiD->registerResource(rp);
+ return rp;
+}
+
+bool QVkTextureRenderTarget::build()
+{
+ if (d.fb)
+ release();
+
+ const QVector<QRhiColorAttachment> colorAttachments = m_desc.colorAttachments();
+ Q_ASSERT(!colorAttachments.isEmpty() || m_desc.depthTexture());
+ Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+ const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVarLengthArray<VkImageView, 8> views;
+
+ d.colorAttCount = colorAttachments.count();
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ QVkTexture *texD = QRHI_RES(QVkTexture, colorAttachments[i].texture());
+ QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, colorAttachments[i].renderBuffer());
+ Q_ASSERT(texD || rbD);
+ if (texD) {
+ Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget));
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = texD->image;
+ viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = texD->vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = colorAttachments[i].level();
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = colorAttachments[i].layer();
+ viewInfo.subresourceRange.layerCount = 1;
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[i]);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target image view: %d", err);
+ return false;
+ }
+ views.append(rtv[i]);
+ if (i == 0) {
+ d.pixelSize = texD->pixelSize();
+ d.sampleCount = texD->samples;
+ }
+ } else if (rbD) {
+ Q_ASSERT(rbD->backingTexture);
+ views.append(rbD->backingTexture->imageView);
+ if (i == 0) {
+ d.pixelSize = rbD->pixelSize();
+ d.sampleCount = rbD->samples;
+ }
+ }
+ }
+ d.dpr = 1;
+
+ if (hasDepthStencil) {
+ if (m_desc.depthTexture()) {
+ QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture());
+ views.append(depthTexD->imageView);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthTexD->pixelSize();
+ d.sampleCount = depthTexD->samples;
+ }
+ } else {
+ QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer());
+ views.append(depthRbD->imageView);
+ if (d.colorAttCount == 0) {
+ d.pixelSize = depthRbD->pixelSize();
+ d.sampleCount = depthRbD->samples;
+ }
+ }
+ d.dsAttCount = 1;
+ } else {
+ d.dsAttCount = 0;
+ }
+
+ d.resolveAttCount = 0;
+ for (int i = 0; i < d.colorAttCount; ++i) {
+ if (colorAttachments[i].resolveTexture()) {
+ QVkTexture *resTexD = QRHI_RES(QVkTexture, colorAttachments[i].resolveTexture());
+ Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
+ d.resolveAttCount += 1;
+
+ VkImageViewCreateInfo viewInfo;
+ memset(&viewInfo, 0, sizeof(viewInfo));
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = resTexD->image;
+ viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = resTexD->vkformat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = colorAttachments[i].resolveLevel();
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = colorAttachments[i].resolveLayer();
+ viewInfo.subresourceRange.layerCount = 1;
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[i]);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target resolve image view: %d", err);
+ return false;
+ }
+ views.append(resrtv[i]);
+ }
+ }
+
+ if (!m_renderPassDesc)
+ qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
+
+ d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
+ Q_ASSERT(d.rp && d.rp->rp);
+
+ VkFramebufferCreateInfo fbInfo;
+ memset(&fbInfo, 0, sizeof(fbInfo));
+ fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbInfo.renderPass = d.rp->rp;
+ fbInfo.attachmentCount = d.colorAttCount + d.dsAttCount + d.resolveAttCount;
+ fbInfo.pAttachments = views.constData();
+ fbInfo.width = d.pixelSize.width();
+ fbInfo.height = d.pixelSize.height();
+ fbInfo.layers = 1;
+
+ VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create framebuffer: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QSize QVkTextureRenderTarget::pixelSize() const
+{
+ return d.pixelSize;
+}
+
+float QVkTextureRenderTarget::devicePixelRatio() const
+{
+ return d.dpr;
+}
+
+int QVkTextureRenderTarget::sampleCount() const
+{
+ return d.sampleCount;
+}
+
+QVkShaderResourceBindings::QVkShaderResourceBindings(QRhiImplementation *rhi)
+ : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QVkShaderResourceBindings::~QVkShaderResourceBindings()
+{
+ release();
+}
+
+void QVkShaderResourceBindings::release()
+{
+ if (!layout)
+ return;
+
+ sortedBindings.clear();
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.shaderResourceBindings.poolIndex = poolIndex;
+ e.shaderResourceBindings.layout = layout;
+
+ poolIndex = -1;
+ layout = VK_NULL_HANDLE;
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ descSets[i] = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkShaderResourceBindings::build()
+{
+ if (layout)
+ release();
+
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ descSets[i] = VK_NULL_HANDLE;
+
+ sortedBindings = m_bindings;
+ std::sort(sortedBindings.begin(), sortedBindings.end(),
+ [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b)
+ {
+ return QRhiShaderResourceBindingPrivate::get(&a)->binding < QRhiShaderResourceBindingPrivate::get(&b)->binding;
+ });
+
+ QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings;
+ for (const QRhiShaderResourceBinding &binding : qAsConst(sortedBindings)) {
+ const QRhiShaderResourceBindingPrivate *b = QRhiShaderResourceBindingPrivate::get(&binding);
+ VkDescriptorSetLayoutBinding vkbinding;
+ memset(&vkbinding, 0, sizeof(vkbinding));
+ vkbinding.binding = b->binding;
+ vkbinding.descriptorType = toVkDescriptorType(b);
+ vkbinding.descriptorCount = 1; // no array support yet
+ vkbinding.stageFlags = toVkShaderStageFlags(b->stage);
+ vkbindings.append(vkbinding);
+ }
+
+ VkDescriptorSetLayoutCreateInfo layoutInfo;
+ memset(&layoutInfo, 0, sizeof(layoutInfo));
+ layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ layoutInfo.bindingCount = uint32_t(vkbindings.count());
+ layoutInfo.pBindings = vkbindings.constData();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create descriptor set layout: %d", err);
+ return false;
+ }
+
+ VkDescriptorSetAllocateInfo allocInfo;
+ memset(&allocInfo, 0, sizeof(allocInfo));
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT;
+ VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT];
+ for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i)
+ layouts[i] = layout;
+ allocInfo.pSetLayouts = layouts;
+ if (!rhiD->allocateDescriptorSet(&allocInfo, descSets, &poolIndex))
+ return false;
+
+ rhiD->updateShaderResourceBindings(this);
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi)
+ : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QVkGraphicsPipeline::~QVkGraphicsPipeline()
+{
+ release();
+}
+
+void QVkGraphicsPipeline::release()
+{
+ if (!pipeline && !layout)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.pipelineState.pipeline = pipeline;
+ e.pipelineState.layout = layout;
+
+ pipeline = VK_NULL_HANDLE;
+ layout = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkGraphicsPipeline::build()
+{
+ if (pipeline)
+ release();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ if (!rhiD->ensurePipelineCache())
+ return false;
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo;
+ memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo));
+ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipelineLayoutInfo.setLayoutCount = 1;
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
+ Q_ASSERT(m_shaderResourceBindings && srbD->layout);
+ pipelineLayoutInfo.pSetLayouts = &srbD->layout;
+ VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create pipeline layout: %d", err);
+ return false;
+ }
+
+ VkGraphicsPipelineCreateInfo pipelineInfo;
+ memset(&pipelineInfo, 0, sizeof(pipelineInfo));
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+
+ QVarLengthArray<VkShaderModule, 4> shaders;
+ QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos;
+ for (const QRhiShaderStage &shaderStage : m_shaderStages) {
+ const QShader bakedShader = shaderStage.shader();
+ const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, shaderStage.shaderVariant() });
+ if (spirv.shader().isEmpty()) {
+ qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
+ return false;
+ }
+ VkShaderModule shader = rhiD->createShader(spirv.shader());
+ if (shader) {
+ shaders.append(shader);
+ VkPipelineShaderStageCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderInfo.stage = toVkShaderStage(shaderStage.type());
+ shaderInfo.module = shader;
+ shaderInfo.pName = spirv.entryPoint().constData();
+ shaderStageCreateInfos.append(shaderInfo);
+ }
+ }
+ pipelineInfo.stageCount = shaderStageCreateInfos.count();
+ pipelineInfo.pStages = shaderStageCreateInfos.constData();
+
+ const QVector<QRhiVertexInputBinding> bindings = m_vertexInputLayout.bindings();
+ QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings;
+ QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates;
+ for (int i = 0, ie = bindings.count(); i != ie; ++i) {
+ const QRhiVertexInputBinding &binding(bindings[i]);
+ VkVertexInputBindingDescription bindingInfo = {
+ uint32_t(i),
+ binding.stride(),
+ binding.classification() == QRhiVertexInputBinding::PerVertex
+ ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE
+ };
+ if (binding.classification() == QRhiVertexInputBinding::PerInstance
+ && binding.instanceStepRate() != 1)
+ {
+ if (rhiD->vertexAttribDivisorAvailable) {
+ nonOneStepRates.append({ uint32_t(i), uint32_t(binding.instanceStepRate()) });
+ } else {
+ qWarning("QRhiVulkan: Instance step rates other than 1 not supported without "
+ "VK_EXT_vertex_attribute_divisor on the device and "
+ "VK_KHR_get_physical_device_properties2 on the instance");
+ }
+ }
+ vertexBindings.append(bindingInfo);
+ }
+ const QVector<QRhiVertexInputAttribute> attributes = m_vertexInputLayout.attributes();
+ QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes;
+ for (const QRhiVertexInputAttribute &attribute : attributes) {
+ VkVertexInputAttributeDescription attributeInfo = {
+ uint32_t(attribute.location()),
+ uint32_t(attribute.binding()),
+ toVkAttributeFormat(attribute.format()),
+ attribute.offset()
+ };
+ vertexAttributes.append(attributeInfo);
+ }
+ VkPipelineVertexInputStateCreateInfo vertexInputInfo;
+ memset(&vertexInputInfo, 0, sizeof(vertexInputInfo));
+ vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ vertexInputInfo.vertexBindingDescriptionCount = vertexBindings.count();
+ vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData();
+ vertexInputInfo.vertexAttributeDescriptionCount = vertexAttributes.count();
+ vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData();
+ VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo;
+ if (!nonOneStepRates.isEmpty()) {
+ memset(&divisorInfo, 0, sizeof(divisorInfo));
+ divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
+ divisorInfo.vertexBindingDivisorCount = nonOneStepRates.count();
+ divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData();
+ vertexInputInfo.pNext = &divisorInfo;
+ }
+ pipelineInfo.pVertexInputState = &vertexInputInfo;
+
+ QVarLengthArray<VkDynamicState, 8> dynEnable;
+ dynEnable << VK_DYNAMIC_STATE_VIEWPORT;
+ dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always
+ if (m_flags.testFlag(QRhiGraphicsPipeline::UsesBlendConstants))
+ dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS;
+ if (m_flags.testFlag(QRhiGraphicsPipeline::UsesStencilRef))
+ dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE;
+
+ VkPipelineDynamicStateCreateInfo dynamicInfo;
+ memset(&dynamicInfo, 0, sizeof(dynamicInfo));
+ dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+ dynamicInfo.dynamicStateCount = dynEnable.count();
+ dynamicInfo.pDynamicStates = dynEnable.constData();
+ pipelineInfo.pDynamicState = &dynamicInfo;
+
+ VkPipelineViewportStateCreateInfo viewportInfo;
+ memset(&viewportInfo, 0, sizeof(viewportInfo));
+ viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ viewportInfo.viewportCount = viewportInfo.scissorCount = 1;
+ pipelineInfo.pViewportState = &viewportInfo;
+
+ VkPipelineInputAssemblyStateCreateInfo inputAsmInfo;
+ memset(&inputAsmInfo, 0, sizeof(inputAsmInfo));
+ inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ inputAsmInfo.topology = toVkTopology(m_topology);
+ inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip);
+ pipelineInfo.pInputAssemblyState = &inputAsmInfo;
+
+ VkPipelineRasterizationStateCreateInfo rastInfo;
+ memset(&rastInfo, 0, sizeof(rastInfo));
+ rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ rastInfo.cullMode = toVkCullMode(m_cullMode);
+ rastInfo.frontFace = toVkFrontFace(m_frontFace);
+ rastInfo.lineWidth = rhiD->hasWideLines ? m_lineWidth : 1.0f;
+ pipelineInfo.pRasterizationState = &rastInfo;
+
+ VkPipelineMultisampleStateCreateInfo msInfo;
+ memset(&msInfo, 0, sizeof(msInfo));
+ msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ msInfo.rasterizationSamples = rhiD->effectiveSampleCount(m_sampleCount);
+ pipelineInfo.pMultisampleState = &msInfo;
+
+ VkPipelineDepthStencilStateCreateInfo dsInfo;
+ memset(&dsInfo, 0, sizeof(dsInfo));
+ dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+ dsInfo.depthTestEnable = m_depthTest;
+ dsInfo.depthWriteEnable = m_depthWrite;
+ dsInfo.depthCompareOp = toVkCompareOp(m_depthOp);
+ dsInfo.stencilTestEnable = m_stencilTest;
+ if (m_stencilTest) {
+ fillVkStencilOpState(&dsInfo.front, m_stencilFront);
+ dsInfo.front.compareMask = m_stencilReadMask;
+ dsInfo.front.writeMask = m_stencilWriteMask;
+ fillVkStencilOpState(&dsInfo.back, m_stencilBack);
+ dsInfo.back.compareMask = m_stencilReadMask;
+ dsInfo.back.writeMask = m_stencilWriteMask;
+ }
+ pipelineInfo.pDepthStencilState = &dsInfo;
+
+ VkPipelineColorBlendStateCreateInfo blendInfo;
+ memset(&blendInfo, 0, sizeof(blendInfo));
+ blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends;
+ for (const QRhiGraphicsPipeline::TargetBlend &b : qAsConst(m_targetBlends)) {
+ VkPipelineColorBlendAttachmentState blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.blendEnable = b.enable;
+ blend.srcColorBlendFactor = toVkBlendFactor(b.srcColor);
+ blend.dstColorBlendFactor = toVkBlendFactor(b.dstColor);
+ blend.colorBlendOp = toVkBlendOp(b.opColor);
+ blend.srcAlphaBlendFactor = toVkBlendFactor(b.srcAlpha);
+ blend.dstAlphaBlendFactor = toVkBlendFactor(b.dstAlpha);
+ blend.alphaBlendOp = toVkBlendOp(b.opAlpha);
+ blend.colorWriteMask = toVkColorComponents(b.colorWrite);
+ vktargetBlends.append(blend);
+ }
+ if (vktargetBlends.isEmpty()) {
+ VkPipelineColorBlendAttachmentState blend;
+ memset(&blend, 0, sizeof(blend));
+ blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
+ | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+ vktargetBlends.append(blend);
+ }
+ blendInfo.attachmentCount = vktargetBlends.count();
+ blendInfo.pAttachments = vktargetBlends.constData();
+ pipelineInfo.pColorBlendState = &blendInfo;
+
+ pipelineInfo.layout = layout;
+
+ Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp);
+ pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp;
+
+ err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
+
+ for (VkShaderModule shader : shaders)
+ rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
+
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create graphics pipeline: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkComputePipeline::QVkComputePipeline(QRhiImplementation *rhi)
+ : QRhiComputePipeline(rhi)
+{
+}
+
+QVkComputePipeline::~QVkComputePipeline()
+{
+ release();
+}
+
+void QVkComputePipeline::release()
+{
+ if (!pipeline && !layout)
+ return;
+
+ QRhiVulkan::DeferredReleaseEntry e;
+ e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline;
+ e.lastActiveFrameSlot = lastActiveFrameSlot;
+
+ e.pipelineState.pipeline = pipeline;
+ e.pipelineState.layout = layout;
+
+ pipeline = VK_NULL_HANDLE;
+ layout = VK_NULL_HANDLE;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->releaseQueue.append(e);
+
+ rhiD->unregisterResource(this);
+}
+
+bool QVkComputePipeline::build()
+{
+ if (pipeline)
+ release();
+
+ QRHI_RES_RHI(QRhiVulkan);
+ if (!rhiD->ensurePipelineCache())
+ return false;
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo;
+ memset(&pipelineLayoutInfo, 0, sizeof(pipelineLayoutInfo));
+ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipelineLayoutInfo.setLayoutCount = 1;
+ QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings);
+ Q_ASSERT(m_shaderResourceBindings && srbD->layout);
+ pipelineLayoutInfo.pSetLayouts = &srbD->layout;
+ VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create pipeline layout: %d", err);
+ return false;
+ }
+
+ VkComputePipelineCreateInfo pipelineInfo;
+ memset(&pipelineInfo, 0, sizeof(pipelineInfo));
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
+ pipelineInfo.layout = layout;
+
+ if (m_shaderStage.type() != QRhiShaderStage::Compute) {
+ qWarning("Compute pipeline requires a compute shader stage");
+ return false;
+ }
+ const QShader bakedShader = m_shaderStage.shader();
+ const QShaderCode spirv = bakedShader.shader({ QShader::SpirvShader, 100, m_shaderStage.shaderVariant() });
+ if (spirv.shader().isEmpty()) {
+ qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader;
+ return false;
+ }
+ if (bakedShader.stage() != QShader::ComputeStage) {
+ qWarning() << bakedShader << "is not a compute shader";
+ return false;
+ }
+ VkShaderModule shader = rhiD->createShader(spirv.shader());
+ VkPipelineShaderStageCreateInfo shaderInfo;
+ memset(&shaderInfo, 0, sizeof(shaderInfo));
+ shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+ shaderInfo.module = shader;
+ shaderInfo.pName = spirv.entryPoint().constData();
+ pipelineInfo.stage = shaderInfo;
+
+ err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline);
+ rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create graphics pipeline: %d", err);
+ return false;
+ }
+
+ lastActiveFrameSlot = -1;
+ generation += 1;
+ rhiD->registerResource(this);
+ return true;
+}
+
+QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi)
+ : QRhiCommandBuffer(rhi)
+{
+ resetState();
+}
+
+QVkCommandBuffer::~QVkCommandBuffer()
+{
+ release();
+}
+
+void QVkCommandBuffer::release()
+{
+ // nothing to do here, cb is not owned by us
+}
+
+QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
+ : QRhiSwapChain(rhi),
+ rtWrapper(rhi),
+ cbWrapper(rhi)
+{
+}
+
+QVkSwapChain::~QVkSwapChain()
+{
+ release();
+}
+
+void QVkSwapChain::release()
+{
+ if (sc == VK_NULL_HANDLE)
+ return;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->swapchains.remove(this);
+ rhiD->releaseSwapChainResources(this);
+ surface = lastConnectedSurface = VK_NULL_HANDLE;
+
+ QRHI_PROF;
+ QRHI_PROF_F(releaseSwapChain(this));
+
+ rhiD->unregisterResource(this);
+}
+
+QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer()
+{
+ return &cbWrapper;
+}
+
+QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget()
+{
+ return &rtWrapper;
+}
+
+QSize QVkSwapChain::surfacePixelSize()
+{
+ if (!ensureSurface())
+ return QSize();
+
+ // The size from the QWindow may not exactly match the surface... so if a
+ // size is reported from the surface, use that.
+ VkSurfaceCapabilitiesKHR surfaceCaps;
+ memset(&surfaceCaps, 0, sizeof(surfaceCaps));
+ QRHI_RES_RHI(QRhiVulkan);
+ rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps);
+ VkExtent2D bufferSize = surfaceCaps.currentExtent;
+ if (bufferSize.width == quint32(-1)) {
+ Q_ASSERT(bufferSize.height == quint32(-1));
+ return m_window->size() * m_window->devicePixelRatio();
+ }
+ return QSize(bufferSize.width, bufferSize.height);
+}
+
+QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor()
+{
+ // not yet built so cannot rely on data computed in buildOrResize()
+
+ if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested
+ return nullptr;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi);
+ if (!rhiD->createDefaultRenderPass(&rp->rp,
+ m_depthStencil != nullptr,
+ samples,
+ colorFormat))
+ {
+ delete rp;
+ return nullptr;
+ }
+
+ rp->ownsRp = true;
+ rhiD->registerResource(rp);
+ return rp;
+}
+
+static inline bool isSrgbFormat(VkFormat format)
+{
+ switch (format) {
+ case VK_FORMAT_R8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8B8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_B8G8R8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_R8G8B8A8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_B8G8R8A8_SRGB:
+ Q_FALLTHROUGH();
+ case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QVkSwapChain::ensureSurface()
+{
+ // Do nothing when already done, however window may change so check the
+ // surface is still the same. Some of the queries below are very expensive
+ // with some implementations so it is important to do the rest only once
+ // per surface.
+
+ Q_ASSERT(m_window);
+ VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(m_window);
+ if (!surf) {
+ qWarning("Failed to get surface for window");
+ return false;
+ }
+ if (surface == surf)
+ return true;
+
+ surface = surf;
+
+ QRHI_RES_RHI(QRhiVulkan);
+ if (rhiD->gfxQueueFamilyIdx != -1) {
+ if (!rhiD->inst->supportsPresent(rhiD->physDev, rhiD->gfxQueueFamilyIdx, m_window)) {
+ qWarning("Presenting not supported on this window");
+ return false;
+ }
+ }
+
+ if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR) {
+ rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"));
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfaceFormatsKHR"));
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>(
+ rhiD->inst->getInstanceProcAddr("vkGetPhysicalDeviceSurfacePresentModesKHR"));
+ if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR
+ || !rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR
+ || !rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR)
+ {
+ qWarning("Physical device surface queries not available");
+ return false;
+ }
+ }
+
+ quint32 formatCount = 0;
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr);
+ QVector<VkSurfaceFormatKHR> formats(formatCount);
+ if (formatCount)
+ rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data());
+
+ const bool srgbRequested = m_flags.testFlag(sRGB);
+ for (quint32 i = 0; i < formatCount; ++i) {
+ if (formats[i].format != VK_FORMAT_UNDEFINED && srgbRequested == isSrgbFormat(formats[i].format)) {
+ colorFormat = formats[i].format;
+ colorSpace = formats[i].colorSpace;
+ break;
+ }
+ }
+
+ samples = rhiD->effectiveSampleCount(m_sampleCount);
+
+ quint32 presModeCount = 0;
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr);
+ QVector<VkPresentModeKHR> presModes(presModeCount);
+ rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, presModes.data());
+ supportedPresentationModes = presModes;
+
+ return true;
+}
+
+bool QVkSwapChain::buildOrResize()
+{
+ QRHI_RES_RHI(QRhiVulkan);
+ const bool needsRegistration = !window || window != m_window;
+
+ // Can be called multiple times due to window resizes - that is not the
+ // same as a simple release+build (as with other resources). Thus no
+ // release() here. See recreateSwapChain().
+
+ // except if the window actually changes
+ if (window && window != m_window)
+ release();
+
+ window = m_window;
+ m_currentPixelSize = surfacePixelSize();
+ pixelSize = m_currentPixelSize;
+
+ if (!rhiD->recreateSwapChain(this)) {
+ qWarning("Failed to create new swapchain");
+ return false;
+ }
+
+ if (needsRegistration)
+ rhiD->swapchains.insert(this);
+
+ if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+ qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+ m_depthStencil->sampleCount(), m_sampleCount);
+ }
+ if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+ qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+ m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+ pixelSize.width(), pixelSize.height());
+ }
+
+ if (!m_renderPassDesc)
+ qWarning("QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
+
+ rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
+ Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp);
+
+ rtWrapper.d.pixelSize = pixelSize;
+ rtWrapper.d.dpr = window->devicePixelRatio();
+ rtWrapper.d.sampleCount = samples;
+ rtWrapper.d.colorAttCount = 1;
+ if (m_depthStencil) {
+ rtWrapper.d.dsAttCount = 1;
+ ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
+ } else {
+ rtWrapper.d.dsAttCount = 0;
+ ds = nullptr;
+ }
+ if (samples > VK_SAMPLE_COUNT_1_BIT)
+ rtWrapper.d.resolveAttCount = 1;
+ else
+ rtWrapper.d.resolveAttCount = 0;
+
+ for (int i = 0; i < bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(imageRes[i]);
+ VkImageView views[3] = { // color, ds, resolve
+ samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView,
+ ds ? ds->imageView : VK_NULL_HANDLE,
+ samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE
+ };
+
+ VkFramebufferCreateInfo fbInfo;
+ memset(&fbInfo, 0, sizeof(fbInfo));
+ fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbInfo.renderPass = rtWrapper.d.rp->rp;
+ fbInfo.attachmentCount = rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount;
+ fbInfo.pAttachments = views;
+ fbInfo.width = pixelSize.width();
+ fbInfo.height = pixelSize.height();
+ fbInfo.layers = 1;
+
+ VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create framebuffer: %d", err);
+ return false;
+ }
+ }
+
+ frameCount = 0;
+
+ QRHI_PROF;
+ QRHI_PROF_F(resizeSwapChain(this, QVK_FRAMES_IN_FLIGHT, samples > VK_SAMPLE_COUNT_1_BIT ? QVK_FRAMES_IN_FLIGHT : 0, samples));
+
+ if (needsRegistration)
+ rhiD->registerResource(this);
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
new file mode 100644
index 0000000000..545ef5ad72
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKAN_H
+#define QRHIVULKAN_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qrhi_p.h>
+#include <QtGui/qvulkaninstance.h> // this is where vulkan.h gets pulled in
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QRhiVulkanInitParams : public QRhiInitParams
+{
+ QVulkanInstance *inst = nullptr;
+ QWindow *window = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanNativeHandles : public QRhiNativeHandles
+{
+ VkPhysicalDevice physDev = VK_NULL_HANDLE;
+ VkDevice dev = VK_NULL_HANDLE;
+ int gfxQueueFamilyIdx = -1;
+ VkQueue gfxQueue = VK_NULL_HANDLE;
+ VkCommandPool cmdPool = VK_NULL_HANDLE;
+ void *vmemAllocator = nullptr;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanTextureNativeHandles : public QRhiNativeHandles
+{
+ VkImage image = VK_NULL_HANDLE;
+ VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL;
+};
+
+struct Q_GUI_EXPORT QRhiVulkanCommandBufferNativeHandles : public QRhiNativeHandles
+{
+ VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkan_p_p.h b/src/gui/rhi/qrhivulkan_p_p.h
new file mode 100644
index 0000000000..31e0eaa585
--- /dev/null
+++ b/src/gui/rhi/qrhivulkan_p_p.h
@@ -0,0 +1,917 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKAN_P_H
+#define QRHIVULKAN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhivulkan_p.h"
+#include "qrhi_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QVulkanFunctions;
+class QVulkanDeviceFunctions;
+
+static const int QVK_FRAMES_IN_FLIGHT = 2;
+
+static const int QVK_DESC_SETS_PER_POOL = 128;
+static const int QVK_UNIFORM_BUFFERS_PER_POOL = 256;
+static const int QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL = 256;
+static const int QVK_STORAGE_BUFFERS_PER_POOL = 128;
+static const int QVK_STORAGE_IMAGES_PER_POOL = 128;
+
+static const int QVK_MAX_ACTIVE_TIMESTAMP_PAIRS = 16;
+
+// no vk_mem_alloc.h available here, void* is good enough
+typedef void * QVkAlloc;
+typedef void * QVkAllocator;
+
+struct QVkBuffer : public QRhiBuffer
+{
+ QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size);
+ ~QVkBuffer();
+ void release() override;
+ bool build() override;
+
+ VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];
+ QVector<QRhiResourceUpdateBatchPrivate::DynamicBufferUpdate> pendingDynamicUpdates[QVK_FRAMES_IN_FLIGHT];
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ struct UsageState {
+ VkAccessFlags access = 0;
+ VkPipelineStageFlags stage = 0;
+ };
+ UsageState usageState[QVK_FRAMES_IN_FLIGHT];
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkTexture;
+
+struct QVkRenderBuffer : public QRhiRenderBuffer
+{
+ QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QVkRenderBuffer();
+ void release() override;
+ bool build() override;
+ QRhiTexture::Format backingFormat() const override;
+
+ VkDeviceMemory memory = VK_NULL_HANDLE;
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ VkSampleCountFlagBits samples;
+ QVkTexture *backingTexture = nullptr;
+ VkFormat vkformat;
+ int lastActiveFrameSlot = -1;
+ friend class QRhiVulkan;
+};
+
+struct QVkTexture : public QRhiTexture
+{
+ QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize,
+ int sampleCount, Flags flags);
+ ~QVkTexture();
+ void release() override;
+ bool build() override;
+ bool buildFrom(const QRhiNativeHandles *src) override;
+ const QRhiNativeHandles *nativeHandles() override;
+
+ bool prepareBuild(QSize *adjustedSize = nullptr);
+ bool finishBuild();
+ VkImageView imageViewForLevel(int level);
+
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ QVkAlloc imageAlloc = nullptr;
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ VkImageView perLevelImageViews[QRhi::MAX_LEVELS];
+ bool owns = true;
+ QRhiVulkanTextureNativeHandles nativeHandlesStruct;
+ struct UsageState {
+ // no tracking of subresource layouts (some operations can keep
+ // subresources in different layouts for some time, but that does not
+ // need to be kept track of)
+ VkImageLayout layout;
+ VkAccessFlags access;
+ VkPipelineStageFlags stage;
+ };
+ UsageState usageState;
+ VkFormat vkformat;
+ uint mipLevelCount = 0;
+ VkSampleCountFlagBits samples;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkSampler : public QRhiSampler
+{
+ QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+ AddressMode u, AddressMode v);
+ ~QVkSampler();
+ void release() override;
+ bool build() override;
+
+ VkSampler sampler = VK_NULL_HANDLE;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+ QVkRenderPassDescriptor(QRhiImplementation *rhi);
+ ~QVkRenderPassDescriptor();
+ void release() override;
+
+ VkRenderPass rp = VK_NULL_HANDLE;
+ bool ownsRp = false;
+ int lastActiveFrameSlot = -1;
+};
+
+struct QVkRenderTargetData
+{
+ VkFramebuffer fb = VK_NULL_HANDLE;
+ QVkRenderPassDescriptor *rp = nullptr;
+ QSize pixelSize;
+ float dpr = 1;
+ int sampleCount = 1;
+ int colorAttCount = 0;
+ int dsAttCount = 0;
+ int resolveAttCount = 0;
+ static const int MAX_COLOR_ATTACHMENTS = 8;
+};
+
+struct QVkReferenceRenderTarget : public QRhiRenderTarget
+{
+ QVkReferenceRenderTarget(QRhiImplementation *rhi);
+ ~QVkReferenceRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QVkRenderTargetData d;
+};
+
+struct QVkTextureRenderTarget : public QRhiTextureRenderTarget
+{
+ QVkTextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+ ~QVkTextureRenderTarget();
+ void release() override;
+
+ QSize pixelSize() const override;
+ float devicePixelRatio() const override;
+ int sampleCount() const override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool build() override;
+
+ QVkRenderTargetData d;
+ VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ int lastActiveFrameSlot = -1;
+ friend class QRhiVulkan;
+};
+
+struct QVkShaderResourceBindings : public QRhiShaderResourceBindings
+{
+ QVkShaderResourceBindings(QRhiImplementation *rhi);
+ ~QVkShaderResourceBindings();
+ void release() override;
+ bool build() override;
+
+ QVector<QRhiShaderResourceBinding> sortedBindings;
+ int poolIndex = -1;
+ VkDescriptorSetLayout layout = VK_NULL_HANDLE;
+ VkDescriptorSet descSets[QVK_FRAMES_IN_FLIGHT]; // multiple sets to support dynamic buffers
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+
+ // Keep track of the generation number of each referenced QRhi* to be able
+ // to detect that the underlying descriptor set became out of date and they
+ // need to be written again with the up-to-date VkBuffer etc. objects.
+ struct BoundUniformBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundSampledTextureData {
+ quint64 texId;
+ uint texGeneration;
+ quint64 samplerId;
+ uint samplerGeneration;
+ };
+ struct BoundStorageImageData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundStorageBufferData {
+ quint64 id;
+ uint generation;
+ };
+ struct BoundResourceData {
+ union {
+ BoundUniformBufferData ubuf;
+ BoundSampledTextureData stex;
+ BoundStorageImageData simage;
+ BoundStorageBufferData sbuf;
+ };
+ };
+ QVector<BoundResourceData> boundResourceData[QVK_FRAMES_IN_FLIGHT];
+
+ friend class QRhiVulkan;
+};
+
+Q_DECLARE_TYPEINFO(QVkShaderResourceBindings::BoundResourceData, Q_MOVABLE_TYPE);
+
+struct QVkGraphicsPipeline : public QRhiGraphicsPipeline
+{
+ QVkGraphicsPipeline(QRhiImplementation *rhi);
+ ~QVkGraphicsPipeline();
+ void release() override;
+ bool build() override;
+
+ VkPipelineLayout layout = VK_NULL_HANDLE;
+ VkPipeline pipeline = VK_NULL_HANDLE;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkComputePipeline : public QRhiComputePipeline
+{
+ QVkComputePipeline(QRhiImplementation *rhi);
+ ~QVkComputePipeline();
+ void release() override;
+ bool build() override;
+
+ VkPipelineLayout layout = VK_NULL_HANDLE;
+ VkPipeline pipeline = VK_NULL_HANDLE;
+ int lastActiveFrameSlot = -1;
+ uint generation = 0;
+ friend class QRhiVulkan;
+};
+
+struct QVkCommandBuffer : public QRhiCommandBuffer
+{
+ QVkCommandBuffer(QRhiImplementation *rhi);
+ ~QVkCommandBuffer();
+ void release() override;
+
+ VkCommandBuffer cb = VK_NULL_HANDLE;
+ QRhiVulkanCommandBufferNativeHandles nativeHandlesStruct;
+
+ const QRhiNativeHandles *nativeHandles() {
+ nativeHandlesStruct.commandBuffer = cb;
+ return &nativeHandlesStruct;
+ }
+
+ enum PassType {
+ NoPass,
+ RenderPass,
+ ComputePass
+ };
+
+ void resetState() {
+ resetCommands();
+ recordingPass = NoPass;
+ currentTarget = nullptr;
+ resetCachedState();
+ }
+
+ void resetCachedState() {
+ currentGraphicsPipeline = nullptr;
+ currentComputePipeline = nullptr;
+ currentPipelineGeneration = 0;
+ currentGraphicsSrb = nullptr;
+ currentComputeSrb = nullptr;
+ currentSrbGeneration = 0;
+ currentDescSetSlot = -1;
+ currentIndexBuffer = VK_NULL_HANDLE;
+ currentIndexOffset = 0;
+ currentIndexFormat = VK_INDEX_TYPE_UINT16;
+ memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
+ memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
+ }
+
+ PassType recordingPass;
+ QRhiRenderTarget *currentTarget;
+ QRhiGraphicsPipeline *currentGraphicsPipeline;
+ QRhiComputePipeline *currentComputePipeline;
+ uint currentPipelineGeneration;
+ QRhiShaderResourceBindings *currentGraphicsSrb;
+ QRhiShaderResourceBindings *currentComputeSrb;
+ uint currentSrbGeneration;
+ int currentDescSetSlot;
+ VkBuffer currentIndexBuffer;
+ quint32 currentIndexOffset;
+ VkIndexType currentIndexFormat;
+ static const int VERTEX_INPUT_RESOURCE_SLOT_COUNT = 32;
+ VkBuffer currentVertexBuffers[VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+ quint32 currentVertexOffsets[VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+
+ struct Command {
+ enum Cmd {
+ CopyBuffer,
+ CopyBufferToImage,
+ CopyImage,
+ CopyImageToBuffer,
+ ImageBarrier,
+ BufferBarrier,
+ BlitImage,
+ BeginRenderPass,
+ EndRenderPass,
+ BindPipeline,
+ BindDescriptorSet,
+ BindVertexBuffer,
+ BindIndexBuffer,
+ SetViewport,
+ SetScissor,
+ SetBlendConstants,
+ SetStencilRef,
+ Draw,
+ DrawIndexed,
+ DebugMarkerBegin,
+ DebugMarkerEnd,
+ DebugMarkerInsert,
+ TransitionPassResources,
+ Dispatch
+ };
+ Cmd cmd;
+
+ union Args {
+ struct {
+ VkBuffer src;
+ VkBuffer dst;
+ VkBufferCopy desc;
+ } copyBuffer;
+ struct {
+ VkBuffer src;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ int count;
+ int bufferImageCopyIndex;
+ } copyBufferToImage;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ VkImageCopy desc;
+ } copyImage;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkBuffer dst;
+ VkBufferImageCopy desc;
+ } copyImageToBuffer;
+ struct {
+ VkPipelineStageFlags srcStageMask;
+ VkPipelineStageFlags dstStageMask;
+ VkImageMemoryBarrier desc;
+ } imageBarrier;
+ struct {
+ VkPipelineStageFlags srcStageMask;
+ VkPipelineStageFlags dstStageMask;
+ VkBufferMemoryBarrier desc;
+ } bufferBarrier;
+ struct {
+ VkImage src;
+ VkImageLayout srcLayout;
+ VkImage dst;
+ VkImageLayout dstLayout;
+ VkFilter filter;
+ VkImageBlit desc;
+ } blitImage;
+ struct {
+ VkRenderPassBeginInfo desc;
+ int clearValueIndex;
+ } beginRenderPass;
+ struct {
+ } endRenderPass;
+ struct {
+ VkPipelineBindPoint bindPoint;
+ VkPipeline pipeline;
+ } bindPipeline;
+ struct {
+ VkPipelineBindPoint bindPoint;
+ VkPipelineLayout pipelineLayout;
+ VkDescriptorSet descSet;
+ int dynamicOffsetCount;
+ int dynamicOffsetIndex;
+ } bindDescriptorSet;
+ struct {
+ int startBinding;
+ int count;
+ int vertexBufferIndex;
+ int vertexBufferOffsetIndex;
+ } bindVertexBuffer;
+ struct {
+ VkBuffer buf;
+ VkDeviceSize ofs;
+ VkIndexType type;
+ } bindIndexBuffer;
+ struct {
+ VkViewport viewport;
+ } setViewport;
+ struct {
+ VkRect2D scissor;
+ } setScissor;
+ struct {
+ float c[4];
+ } setBlendConstants;
+ struct {
+ uint32_t ref;
+ } setStencilRef;
+ struct {
+ uint32_t vertexCount;
+ uint32_t instanceCount;
+ uint32_t firstVertex;
+ uint32_t firstInstance;
+ } draw;
+ struct {
+ uint32_t indexCount;
+ uint32_t instanceCount;
+ uint32_t firstIndex;
+ int32_t vertexOffset;
+ uint32_t firstInstance;
+ } drawIndexed;
+ struct {
+ VkDebugMarkerMarkerInfoEXT marker;
+ int markerNameIndex;
+ } debugMarkerBegin;
+ struct {
+ } debugMarkerEnd;
+ struct {
+ VkDebugMarkerMarkerInfoEXT marker;
+ } debugMarkerInsert;
+ struct {
+ int trackerIndex;
+ } transitionResources;
+ struct {
+ int x, y, z;
+ } dispatch;
+ } args;
+ };
+ QVector<Command> commands;
+ QVarLengthArray<QRhiPassResourceTracker, 8> passResTrackers;
+ int currentPassResTrackerIndex;
+
+ void resetCommands() {
+ commands.clear();
+ passResTrackers.clear();
+ currentPassResTrackerIndex = -1;
+ resetPools();
+ }
+
+ void resetPools() {
+ pools.clearValue.clear();
+ pools.bufferImageCopy.clear();
+ pools.dynamicOffset.clear();
+ pools.vertexBuffer.clear();
+ pools.vertexBufferOffset.clear();
+ pools.debugMarkerName.clear();
+ }
+
+ struct {
+ QVarLengthArray<VkClearValue, 4> clearValue;
+ QVarLengthArray<VkBufferImageCopy, 16> bufferImageCopy;
+ QVarLengthArray<uint32_t, 4> dynamicOffset;
+ QVarLengthArray<VkBuffer, 4> vertexBuffer;
+ QVarLengthArray<VkDeviceSize, 4> vertexBufferOffset;
+ QVarLengthArray<QByteArray, 4> debugMarkerName;
+ } pools;
+
+ friend class QRhiVulkan;
+};
+
+Q_DECLARE_TYPEINFO(QVkCommandBuffer::Command, Q_MOVABLE_TYPE);
+
+struct QVkSwapChain : public QRhiSwapChain
+{
+ QVkSwapChain(QRhiImplementation *rhi);
+ ~QVkSwapChain();
+ void release() override;
+
+ QRhiCommandBuffer *currentFrameCommandBuffer() override;
+ QRhiRenderTarget *currentFrameRenderTarget() override;
+
+ QSize surfacePixelSize() override;
+
+ QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+ bool buildOrResize() override;
+
+ bool ensureSurface();
+
+ static const quint32 MAX_BUFFER_COUNT = 3;
+
+ QWindow *window = nullptr;
+ QSize pixelSize;
+ bool supportsReadback = false;
+ VkSwapchainKHR sc = VK_NULL_HANDLE;
+ int bufferCount = 0;
+ VkSurfaceKHR surface = VK_NULL_HANDLE;
+ VkSurfaceKHR lastConnectedSurface = VK_NULL_HANDLE;
+ VkFormat colorFormat = VK_FORMAT_B8G8R8A8_UNORM;
+ VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
+ QVkRenderBuffer *ds = nullptr;
+ VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
+ QVector<VkPresentModeKHR> supportedPresentationModes;
+ VkDeviceMemory msaaImageMem = VK_NULL_HANDLE;
+ QVkReferenceRenderTarget rtWrapper;
+ QVkCommandBuffer cbWrapper;
+
+ struct ImageResources {
+ VkImage image = VK_NULL_HANDLE;
+ VkImageView imageView = VK_NULL_HANDLE;
+ VkFramebuffer fb = VK_NULL_HANDLE;
+ VkImage msaaImage = VK_NULL_HANDLE;
+ VkImageView msaaImageView = VK_NULL_HANDLE;
+ enum LastUse {
+ ScImageUseNone,
+ ScImageUseRender,
+ ScImageUseTransferSource
+ };
+ LastUse lastUse = ScImageUseNone;
+ } imageRes[MAX_BUFFER_COUNT];
+
+ struct FrameResources {
+ VkFence imageFence = VK_NULL_HANDLE;
+ bool imageFenceWaitable = false;
+ VkSemaphore imageSem = VK_NULL_HANDLE;
+ VkSemaphore drawSem = VK_NULL_HANDLE;
+ bool imageAcquired = false;
+ bool imageSemWaitable = false;
+ quint32 imageIndex = 0;
+ VkCommandBuffer cmdBuf = VK_NULL_HANDLE;
+ VkFence cmdFence = VK_NULL_HANDLE;
+ bool cmdFenceWaitable = false;
+ int timestampQueryIndex = -1;
+ } frameRes[QVK_FRAMES_IN_FLIGHT];
+
+ quint32 currentImageIndex = 0; // index in imageRes
+ quint32 currentFrameSlot = 0; // index in frameRes
+ int frameCount = 0;
+
+ friend class QRhiVulkan;
+};
+
+class QRhiVulkan : public QRhiImplementation
+{
+public:
+ QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice = nullptr);
+
+ bool create(QRhi::Flags flags) override;
+ void destroy() override;
+
+ QRhiGraphicsPipeline *createGraphicsPipeline() override;
+ QRhiComputePipeline *createComputePipeline() override;
+ QRhiShaderResourceBindings *createShaderResourceBindings() override;
+ QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+ QRhiBuffer::UsageFlags usage,
+ int size) override;
+ QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiRenderBuffer::Flags flags) override;
+ QRhiTexture *createTexture(QRhiTexture::Format format,
+ const QSize &pixelSize,
+ int sampleCount,
+ QRhiTexture::Flags flags) override;
+ QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+ QRhiSampler::Filter mipmapMode,
+ QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v) override;
+
+ QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+ QRhiTextureRenderTarget::Flags flags) override;
+
+ QRhiSwapChain *createSwapChain() override;
+ QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+ QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+ QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb) override;
+ QRhi::FrameOpResult endOffscreenFrame() override;
+ QRhi::FrameOpResult finish() override;
+
+ void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void beginPass(QRhiCommandBuffer *cb,
+ QRhiRenderTarget *rt,
+ const QColor &colorClearValue,
+ const QRhiDepthStencilClearValue &depthStencilClearValue,
+ QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+ void setGraphicsPipeline(QRhiCommandBuffer *cb,
+ QRhiGraphicsPipeline *ps) override;
+
+ void setShaderResources(QRhiCommandBuffer *cb,
+ QRhiShaderResourceBindings *srb,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+ void setVertexInput(QRhiCommandBuffer *cb,
+ int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+ QRhiBuffer *indexBuf, quint32 indexOffset,
+ QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+ void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+ void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+ void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+ void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+ void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+ quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+ void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+ quint32 instanceCount, quint32 firstIndex,
+ qint32 vertexOffset, quint32 firstInstance) override;
+
+ void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+ void debugMarkEnd(QRhiCommandBuffer *cb) override;
+ void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+ void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+ void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+ void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+ const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+ void beginExternal(QRhiCommandBuffer *cb) override;
+ void endExternal(QRhiCommandBuffer *cb) override;
+
+ QVector<int> supportedSampleCounts() const override;
+ int ubufAlignment() const override;
+ bool isYUpInFramebuffer() const override;
+ bool isYUpInNDC() const override;
+ bool isClipDepthZeroToOne() const override;
+ QMatrix4x4 clipSpaceCorrMatrix() const override;
+ bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+ bool isFeatureSupported(QRhi::Feature feature) const override;
+ int resourceLimit(QRhi::ResourceLimit limit) const override;
+ const QRhiNativeHandles *nativeHandles() override;
+ void sendVMemStatsToProfiler() override;
+ void makeThreadLocalNativeContextCurrent() override;
+
+ VkResult createDescriptorPool(VkDescriptorPool *pool);
+ bool allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex);
+ uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex);
+ bool createTransientImage(VkFormat format, const QSize &pixelSize, VkImageUsageFlags usage,
+ VkImageAspectFlags aspectMask, VkSampleCountFlagBits samples,
+ VkDeviceMemory *mem, VkImage *images, VkImageView *views, int count);
+
+ bool recreateSwapChain(QRhiSwapChain *swapChain);
+ void releaseSwapChainResources(QRhiSwapChain *swapChain);
+
+ VkFormat optimalDepthStencilFormat();
+ VkSampleCountFlagBits effectiveSampleCount(int sampleCount);
+ bool createDefaultRenderPass(VkRenderPass *rp,
+ bool hasDepthStencil,
+ VkSampleCountFlagBits samples,
+ VkFormat colorFormat);
+ bool createOffscreenRenderPass(VkRenderPass *rp,
+ const QVector<QRhiColorAttachment> &colorAttachments,
+ bool preserveColor,
+ bool preserveDs,
+ QRhiRenderBuffer *depthStencilBuffer,
+ QRhiTexture *depthTexture);
+ bool ensurePipelineCache();
+ VkShaderModule createShader(const QByteArray &spirv);
+
+ void prepareNewFrame(QRhiCommandBuffer *cb);
+ QRhi::FrameOpResult startCommandBuffer(VkCommandBuffer *cb);
+ QRhi::FrameOpResult endAndSubmitCommandBuffer(VkCommandBuffer cb, VkFence cmdFence,
+ VkSemaphore *waitSem, VkSemaphore *signalSem);
+ void waitCommandCompletion(int frameSlot);
+ VkDeviceSize subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const;
+ using BufferImageCopyList = QVarLengthArray<VkBufferImageCopy, 16>;
+ void prepareUploadSubres(QVkTexture *texD, int layer, int level,
+ const QRhiTextureSubresourceUploadDescription &subresDesc,
+ size_t *curOfs, void *mp,
+ BufferImageCopyList *copyInfos);
+ void enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates);
+ void executeBufferHostWritesForCurrentFrame(QVkBuffer *bufD);
+ void enqueueTransitionPassResources(QVkCommandBuffer *cbD);
+ void recordCommandBuffer(QVkCommandBuffer *cbD);
+ void trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker,
+ QVkBuffer *bufD,
+ int slot,
+ QRhiPassResourceTracker::BufferAccess access,
+ QRhiPassResourceTracker::BufferStage stage);
+ void trackedRegisterTexture(QRhiPassResourceTracker *passResTracker,
+ QVkTexture *texD,
+ QRhiPassResourceTracker::TextureAccess access,
+ QRhiPassResourceTracker::TextureStage stage);
+ void recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker);
+ void activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD);
+ void executeDeferredReleases(bool forced = false);
+ void finishActiveReadbacks(bool forced = false);
+
+ void setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot = -1);
+ void trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot,
+ VkAccessFlags access, VkPipelineStageFlags stage);
+ void trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD,
+ VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage);
+ void subresourceBarrier(QVkCommandBuffer *cbD, VkImage image,
+ VkImageLayout oldLayout, VkImageLayout newLayout,
+ VkAccessFlags srcAccess, VkAccessFlags dstAccess,
+ VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage,
+ int startLayer, int layerCount,
+ int startLevel, int levelCount);
+ void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1);
+
+ QVulkanInstance *inst = nullptr;
+ QWindow *maybeWindow = nullptr;
+ bool importedDevice = false;
+ VkPhysicalDevice physDev = VK_NULL_HANDLE;
+ VkDevice dev = VK_NULL_HANDLE;
+ bool importedCmdPool = false;
+ VkCommandPool cmdPool = VK_NULL_HANDLE;
+ int gfxQueueFamilyIdx = -1;
+ VkQueue gfxQueue = VK_NULL_HANDLE;
+ bool hasCompute = false;
+ quint32 timestampValidBits = 0;
+ bool importedAllocator = false;
+ QVkAllocator allocator = nullptr;
+ QVulkanFunctions *f = nullptr;
+ QVulkanDeviceFunctions *df = nullptr;
+ VkPhysicalDeviceFeatures physDevFeatures;
+ VkPhysicalDeviceProperties physDevProperties;
+ VkDeviceSize ubufAlign;
+ VkDeviceSize texbufAlign;
+ bool hasWideLines = false;
+
+ bool debugMarkersAvailable = false;
+ bool vertexAttribDivisorAvailable = false;
+ PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBegin = nullptr;
+ PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEnd = nullptr;
+ PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsert = nullptr;
+ PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectName = nullptr;
+
+ PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr;
+ PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
+ PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
+ PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
+ PFN_vkQueuePresentKHR vkQueuePresentKHR;
+ PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
+ PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
+ PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
+
+ VkPipelineCache pipelineCache = VK_NULL_HANDLE;
+ struct DescriptorPoolData {
+ DescriptorPoolData() { }
+ DescriptorPoolData(VkDescriptorPool pool_)
+ : pool(pool_)
+ { }
+ VkDescriptorPool pool = VK_NULL_HANDLE;
+ int refCount = 0;
+ int allocedDescSets = 0;
+ };
+ QVector<DescriptorPoolData> descriptorPools;
+
+ VkQueryPool timestampQueryPool = VK_NULL_HANDLE;
+ QBitArray timestampQueryPoolMap;
+
+ VkFormat optimalDsFormat = VK_FORMAT_UNDEFINED;
+ QMatrix4x4 clipCorrectMatrix;
+
+ QVkSwapChain *currentSwapChain = nullptr;
+ QSet<QVkSwapChain *> swapchains;
+ QRhiVulkanNativeHandles nativeHandlesStruct;
+
+ struct OffscreenFrame {
+ OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+ bool active = false;
+ QVkCommandBuffer cbWrapper;
+ VkFence cmdFence = VK_NULL_HANDLE;
+ } ofr;
+
+ struct ActiveReadback {
+ int activeFrameSlot = -1;
+ QRhiReadbackDescription desc;
+ QRhiReadbackResult *result;
+ VkBuffer buf;
+ QVkAlloc bufAlloc;
+ quint32 bufSize;
+ QSize pixelSize;
+ QRhiTexture::Format format;
+ };
+ QVector<ActiveReadback> activeReadbacks;
+
+ struct DeferredReleaseEntry {
+ enum Type {
+ Pipeline,
+ ShaderResourceBindings,
+ Buffer,
+ RenderBuffer,
+ Texture,
+ Sampler,
+ TextureRenderTarget,
+ RenderPass,
+ StagingBuffer
+ };
+ Type type;
+ int lastActiveFrameSlot; // -1 if not used otherwise 0..FRAMES_IN_FLIGHT-1
+ union {
+ struct {
+ VkPipeline pipeline;
+ VkPipelineLayout layout;
+ } pipelineState;
+ struct {
+ int poolIndex;
+ VkDescriptorSetLayout layout;
+ } shaderResourceBindings;
+ struct {
+ VkBuffer buffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc allocations[QVK_FRAMES_IN_FLIGHT];
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ } buffer;
+ struct {
+ VkDeviceMemory memory;
+ VkImage image;
+ VkImageView imageView;
+ } renderBuffer;
+ struct {
+ VkImage image;
+ VkImageView imageView;
+ QVkAlloc allocation;
+ VkBuffer stagingBuffers[QVK_FRAMES_IN_FLIGHT];
+ QVkAlloc stagingAllocations[QVK_FRAMES_IN_FLIGHT];
+ VkImageView extraImageViews[QRhi::MAX_LEVELS];
+ } texture;
+ struct {
+ VkSampler sampler;
+ } sampler;
+ struct {
+ VkFramebuffer fb;
+ VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ } textureRenderTarget;
+ struct {
+ VkRenderPass rp;
+ } renderPass;
+ struct {
+ VkBuffer stagingBuffer;
+ QVkAlloc stagingAllocation;
+ } stagingBuffer;
+ };
+ };
+ QVector<DeferredReleaseEntry> releaseQueue;
+};
+
+Q_DECLARE_TYPEINFO(QRhiVulkan::DescriptorPoolData, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiVulkan::DeferredReleaseEntry, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiVulkan::ActiveReadback, Q_MOVABLE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhivulkanext_p.h b/src/gui/rhi/qrhivulkanext_p.h
new file mode 100644
index 0000000000..67a63e07e0
--- /dev/null
+++ b/src/gui/rhi/qrhivulkanext_p.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt RHI module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QRHIVULKANEXT_P_H
+#define QRHIVULKANEXT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhivulkan_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#ifndef VK_EXT_vertex_attribute_divisor
+#define VK_EXT_vertex_attribute_divisor 1
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_SPEC_VERSION 2
+#define VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME "VK_EXT_vertex_attribute_divisor"
+
+typedef struct VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT {
+ VkStructureType sType;
+ void* pNext;
+ uint32_t maxVertexAttribDivisor;
+} VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT;
+
+typedef struct VkVertexInputBindingDivisorDescriptionEXT {
+ uint32_t binding;
+ uint32_t divisor;
+} VkVertexInputBindingDivisorDescriptionEXT;
+
+typedef struct VkPipelineVertexInputDivisorStateCreateInfoEXT {
+ VkStructureType sType;
+ const void* pNext;
+ uint32_t vertexBindingDivisorCount;
+ const VkVertexInputBindingDivisorDescriptionEXT* pVertexBindingDivisors;
+} VkPipelineVertexInputDivisorStateCreateInfoEXT;
+#endif // VK_EXT_vertex_attribute_divisor
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp
new file mode 100644
index 0000000000..9098180f69
--- /dev/null
+++ b/src/gui/rhi/qshader.cpp
@@ -0,0 +1,585 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qshader_p_p.h"
+#include <QDataStream>
+#include <QBuffer>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QShader
+ \inmodule QtRhi
+
+ \brief Contains multiple versions of a shader translated to multiple shading languages,
+ together with reflection metadata.
+
+ QShader is the entry point to shader code in the graphics API agnostic
+ Qt world. Instead of using GLSL shader sources, as was the custom with Qt
+ 5.x, new graphics systems with backends for multiple graphics APIs, such
+ as, Vulkan, Metal, Direct3D, and OpenGL, take QShader as their input
+ whenever a shader needs to be specified.
+
+ A QShader instance is empty and thus invalid by default. To get a useful
+ instance, the two typical methods are:
+
+ \list
+
+ \li Generate the contents offline, during build time or earlier, using the
+ \c qsb command line tool. The result is a binary file that is shipped with
+ the application, read via QIODevice::readAll(), and then deserialized via
+ fromSerialized(). For more information, see QShaderBaker.
+
+ \li Generate at run time via QShaderBaker. This is an expensive operation,
+ but allows applications to use user-provided or dynamically generated
+ shader source strings.
+
+ \endlist
+
+ When used together with the Qt Rendering Hardware Interface and its
+ classes, like QRhiGraphicsPipeline, no further action is needed from the
+ application's side as these classes are prepared to consume a QShader
+ whenever a shader needs to be specified for a given stage of the graphics
+ pipeline.
+
+ Alternatively, applications can access
+
+ \list
+
+ \li the source or byte code for any of the shading language versions that
+ are included in the QShader,
+
+ \li the name of the entry point for the shader,
+
+ \li the reflection metadata containing a description of the shader's
+ inputs, outputs and resources like uniform blocks. This is essential when
+ an application or framework needs to discover the inputs of a shader at
+ runtime due to not having advance knowledge of the vertex attributes or the
+ layout of the uniform buffers used by the shader.
+
+ \endlist
+
+ QShader makes no assumption about the shading language that was used
+ as the source for generating the various versions and variants that are
+ included in it.
+
+ QShader uses implicit sharing similarly to many core Qt types, and so
+ can be returned or passed by value. Detach happens implicitly when calling
+ a setter.
+
+ For reference, QRhi expects that a QShader suitable for all its
+ backends contains at least the following:
+
+ \list
+
+ \li SPIR-V 1.0 bytecode suitable for Vulkan 1.0 or newer
+
+ \li GLSL/ES 100 source code suitable for OpenGL ES 2.0 or newer
+
+ \li GLSL 120 source code suitable for OpenGL 2.1
+
+ \li HLSL Shader Model 5.0 source code or the corresponding DXBC bytecode suitable for Direct3D 11
+
+ \li Metal Shading Language 1.2 source code or the corresponding bytecode suitable for Metal
+
+ \endlist
+
+ \sa QShaderBaker
+ */
+
+/*!
+ \enum QShader::Stage
+ Describes the stage of the graphics pipeline the shader is suitable for.
+
+ \value VertexStage Vertex shader
+ \value TessellationControlStage Tessellation control (hull) shader
+ \value TessellationEvaluationStage Tessellation evaluation (domain) shader
+ \value GeometryStage Geometry shader
+ \value FragmentStage Fragment (pixel) shader
+ \value ComputeStage Compute shader
+ */
+
+/*!
+ \class QShaderVersion
+ \inmodule QtRhi
+
+ \brief Specifies the shading language version.
+
+ While languages like SPIR-V or the Metal Shading Language use traditional
+ version numbers, shaders for other APIs can use slightly different
+ versioning schemes. All those are mapped to a single version number in
+ here, however. For HLSL, the version refers to the Shader Model version,
+ like 5.0, 5.1, or 6.0. For GLSL an additional flag is needed to choose
+ between GLSL and GLSL/ES.
+
+ Below is a list with the most common examples of shader versions for
+ different graphics APIs:
+
+ \list
+
+ \li Vulkan (SPIR-V): 100
+ \li OpenGL: 120, 330, 440, etc.
+ \li OpenGL ES: 100 with GlslEs, 300 with GlslEs, etc.
+ \li Direct3D: 50, 51, 60
+ \li Metal: 12, 20
+ \endlist
+
+ A default constructed QShaderVersion contains a version of 100 and no
+ flags set.
+ */
+
+/*!
+ \enum QShaderVersion::Flag
+
+ Describes the flags that can be set.
+
+ \value GlslEs Indicates that GLSL/ES is meant in combination with GlslShader
+ */
+
+/*!
+ \class QShaderKey
+ \inmodule QtRhi
+
+ \brief Specifies the shading language, the version with flags, and the variant.
+
+ A default constructed QShaderKey has source set to SpirvShader and
+ sourceVersion set to 100. sourceVariant defaults to StandardShader.
+ */
+
+/*!
+ \enum QShader::Source
+ Describes what kind of shader code an entry contains.
+
+ \value SpirvShader SPIR-V
+ \value GlslShader GLSL
+ \value HlslShader HLSL
+ \value DxbcShader Direct3D bytecode (HLSL compiled by \c fxc)
+ \value MslShader Metal Shading Language
+ \value DxilShader Direct3D bytecode (HLSL compiled by \c dxc)
+ \value MetalLibShader Pre-compiled Metal bytecode
+ */
+
+/*!
+ \enum QShader::Variant
+ Describes what kind of shader code an entry contains.
+
+ \value StandardShader The normal, unmodified version of the shader code.
+ \value BatchableVertexShader Vertex shader rewritten to be suitable for Qt Quick scenegraph batching.
+ */
+
+/*!
+ \class QShaderCode
+ \inmodule QtRhi
+
+ \brief Contains source or binary code for a shader and additional metadata.
+
+ When shader() is empty after retrieving a QShaderCode instance from
+ QShader, it indicates no shader code was found for the requested key.
+ */
+
+static const int QSB_VERSION = 1;
+
+/*!
+ Constructs a new, empty (and thus invalid) QShader instance.
+ */
+QShader::QShader()
+ : d(new QShaderPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QShader::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QShader::QShader(const QShader &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QShader &QShader::operator=(const QShader &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QShader::~QShader()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return true if the QShader contains at least one shader version.
+ */
+bool QShader::isValid() const
+{
+ return !d->shaders.isEmpty();
+}
+
+/*!
+ \return the pipeline stage the shader is meant for.
+ */
+QShader::Stage QShader::stage() const
+{
+ return d->stage;
+}
+
+/*!
+ Sets the pipeline \a stage.
+ */
+void QShader::setStage(Stage stage)
+{
+ if (stage != d->stage) {
+ detach();
+ d->stage = stage;
+ }
+}
+
+/*!
+ \return the reflection metadata for the shader.
+ */
+QShaderDescription QShader::description() const
+{
+ return d->desc;
+}
+
+/*!
+ Sets the reflection metadata to \a desc.
+ */
+void QShader::setDescription(const QShaderDescription &desc)
+{
+ detach();
+ d->desc = desc;
+}
+
+/*!
+ \return the list of available shader versions
+ */
+QVector<QShaderKey> QShader::availableShaders() const
+{
+ return d->shaders.keys().toVector();
+}
+
+/*!
+ \return the source or binary code for a given shader version specified by \a key.
+ */
+QShaderCode QShader::shader(const QShaderKey &key) const
+{
+ return d->shaders.value(key);
+}
+
+/*!
+ Stores the source or binary \a shader code for a given shader version specified by \a key.
+ */
+void QShader::setShader(const QShaderKey &key, const QShaderCode &shader)
+{
+ if (d->shaders.value(key) == shader)
+ return;
+
+ detach();
+ d->shaders[key] = shader;
+}
+
+/*!
+ Removes the source or binary shader code for a given \a key.
+ Does nothing when not found.
+ */
+void QShader::removeShader(const QShaderKey &key)
+{
+ auto it = d->shaders.find(key);
+ if (it == d->shaders.end())
+ return;
+
+ detach();
+ d->shaders.erase(it);
+}
+
+/*!
+ \return a serialized binary version of all the data held by the
+ QShader, suitable for writing to files or other I/O devices.
+
+ \sa fromSerialized()
+ */
+QByteArray QShader::serialized() const
+{
+ QBuffer buf;
+ QDataStream ds(&buf);
+ ds.setVersion(QDataStream::Qt_5_10);
+ if (!buf.open(QIODevice::WriteOnly))
+ return QByteArray();
+
+ ds << QSB_VERSION;
+ ds << d->stage;
+ ds << d->desc.toBinaryJson();
+ ds << d->shaders.count();
+ for (auto it = d->shaders.cbegin(), itEnd = d->shaders.cend(); it != itEnd; ++it) {
+ const QShaderKey &k(it.key());
+ ds << k.source();
+ ds << k.sourceVersion().version();
+ ds << k.sourceVersion().flags();
+ ds << k.sourceVariant();
+ const QShaderCode &shader(d->shaders.value(k));
+ ds << shader.shader();
+ ds << shader.entryPoint();
+ }
+
+ return qCompress(buf.buffer());
+}
+
+/*!
+ Creates a new QShader instance from the given \a data.
+
+ \sa serialized()
+ */
+QShader QShader::fromSerialized(const QByteArray &data)
+{
+ QByteArray udata = qUncompress(data);
+ QBuffer buf(&udata);
+ QDataStream ds(&buf);
+ ds.setVersion(QDataStream::Qt_5_10);
+ if (!buf.open(QIODevice::ReadOnly))
+ return QShader();
+
+ QShader bs;
+ QShaderPrivate *d = QShaderPrivate::get(&bs);
+ Q_ASSERT(d->ref.loadRelaxed() == 1); // must be detached
+ int intVal;
+ ds >> intVal;
+ if (intVal != QSB_VERSION)
+ return QShader();
+
+ ds >> intVal;
+ d->stage = Stage(intVal);
+ QByteArray descBin;
+ ds >> descBin;
+ d->desc = QShaderDescription::fromBinaryJson(descBin);
+ int count;
+ ds >> count;
+ for (int i = 0; i < count; ++i) {
+ QShaderKey k;
+ ds >> intVal;
+ k.setSource(Source(intVal));
+ QShaderVersion ver;
+ ds >> intVal;
+ ver.setVersion(intVal);
+ ds >> intVal;
+ ver.setFlags(QShaderVersion::Flags(intVal));
+ k.setSourceVersion(ver);
+ ds >> intVal;
+ k.setSourceVariant(Variant(intVal));
+ QShaderCode shader;
+ QByteArray s;
+ ds >> s;
+ shader.setShader(s);
+ ds >> s;
+ shader.setEntryPoint(s);
+ d->shaders[k] = shader;
+ }
+
+ return bs;
+}
+
+QShaderVersion::QShaderVersion(int v, Flags f)
+ : m_version(v), m_flags(f)
+{
+}
+
+QShaderCode::QShaderCode(const QByteArray &code, const QByteArray &entry)
+ : m_shader(code), m_entryPoint(entry)
+{
+}
+
+QShaderKey::QShaderKey(QShader::Source s,
+ const QShaderVersion &sver,
+ QShader::Variant svar)
+ : m_source(s),
+ m_sourceVersion(sver),
+ m_sourceVariant(svar)
+{
+}
+
+/*!
+ Returns \c true if the two QShader objects \a a and \a b are equal,
+ meaning they are for the same stage with matching sets of shader source or
+ binary code.
+
+ \relates QShader
+ */
+bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW
+{
+ return lhs.d->stage == rhs.d->stage
+ && lhs.d->shaders == rhs.d->shaders;
+ // do not bother with desc, if the shader code is the same, the description must match too
+}
+
+/*!
+ \fn bool operator!=(const QShader &lhs, const QShader &rhs)
+
+ Returns \c false if the values in the two QShader objects \a a and \a b
+ are equal; otherwise returns \c true.
+
+ \relates QShader
+ */
+
+/*!
+ Returns the hash value for \a s, using \a seed to seed the calculation.
+
+ \relates QShader
+ */
+uint qHash(const QShader &s, uint seed) Q_DECL_NOTHROW
+{
+ uint h = s.stage();
+ for (auto it = s.d->shaders.constBegin(), itEnd = s.d->shaders.constEnd(); it != itEnd; ++it)
+ h += qHash(it.key(), seed) + qHash(it.value().shader(), seed);
+ return h;
+}
+
+/*!
+ Returns \c true if the two QShaderVersion objects \a a and \a b are
+ equal.
+
+ \relates QShaderVersion
+ */
+bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW
+{
+ return lhs.version() == rhs.version() && lhs.flags() == rhs.flags();
+}
+
+/*!
+ \fn bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs)
+
+ Returns \c false if the values in the two QShaderVersion objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderVersion
+ */
+
+/*!
+ Returns \c true if the two QShaderKey objects \a a and \a b are equal.
+
+ \relates QShaderKey
+ */
+bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW
+{
+ return lhs.source() == rhs.source() && lhs.sourceVersion() == rhs.sourceVersion()
+ && lhs.sourceVariant() == rhs.sourceVariant();
+}
+
+/*!
+ \fn bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs)
+
+ Returns \c false if the values in the two QShaderKey objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderKey
+ */
+
+/*!
+ Returns the hash value for \a k, using \a seed to seed the calculation.
+
+ \relates QShaderKey
+ */
+uint qHash(const QShaderKey &k, uint seed) Q_DECL_NOTHROW
+{
+ return seed + 10 * k.source() + k.sourceVersion().version() + k.sourceVersion().flags() + k.sourceVariant();
+}
+
+/*!
+ Returns \c true if the two QShaderCode objects \a a and \a b are equal.
+
+ \relates QShaderCode
+ */
+bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW
+{
+ return lhs.shader() == rhs.shader() && lhs.entryPoint() == rhs.entryPoint();
+}
+
+/*!
+ \fn bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs)
+
+ Returns \c false if the values in the two QShaderCode objects \a a
+ and \a b are equal; otherwise returns \c true.
+
+ \relates QShaderCode
+ */
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QShader &bs)
+{
+ const QShaderPrivate *d = bs.d;
+ QDebugStateSaver saver(dbg);
+
+ dbg.nospace() << "QShader("
+ << "stage=" << d->stage
+ << " shaders=" << d->shaders.keys()
+ << " desc.isValid=" << d->desc.isValid()
+ << ')';
+
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderKey &k)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "ShaderKey(" << k.source()
+ << " " << k.sourceVersion()
+ << " " << k.sourceVariant() << ")";
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderVersion &v)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "Version(" << v.version() << " " << v.flags() << ")";
+ return dbg;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h
new file mode 100644
index 0000000000..243842a95a
--- /dev/null
+++ b/src/gui/rhi/qshader_p.h
@@ -0,0 +1,224 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADER_P_H
+#define QSHADER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <private/qshaderdescription_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QShaderPrivate;
+class QShaderKey;
+
+class Q_GUI_EXPORT QShaderVersion
+{
+public:
+ enum Flag {
+ GlslEs = 0x01
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ QShaderVersion() = default;
+ QShaderVersion(int v, Flags f = Flags());
+
+ int version() const { return m_version; }
+ void setVersion(int v) { m_version = v; }
+
+ Flags flags() const { return m_flags; }
+ void setFlags(Flags f) { m_flags = f; }
+
+private:
+ int m_version = 100;
+ Flags m_flags;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderVersion::Flags)
+Q_DECLARE_TYPEINFO(QShaderVersion, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QShaderCode
+{
+public:
+ QShaderCode() = default;
+ QShaderCode(const QByteArray &code, const QByteArray &entry = QByteArray());
+
+ QByteArray shader() const { return m_shader; }
+ void setShader(const QByteArray &code) { m_shader = code; }
+
+ QByteArray entryPoint() const { return m_entryPoint; }
+ void setEntryPoint(const QByteArray &entry) { m_entryPoint = entry; }
+
+private:
+ QByteArray m_shader;
+ QByteArray m_entryPoint;
+};
+
+Q_DECLARE_TYPEINFO(QShaderCode, Q_MOVABLE_TYPE);
+
+class Q_GUI_EXPORT QShader
+{
+public:
+ enum Stage {
+ VertexStage = 0,
+ TessellationControlStage,
+ TessellationEvaluationStage,
+ GeometryStage,
+ FragmentStage,
+ ComputeStage
+ };
+
+ enum Source {
+ SpirvShader = 0,
+ GlslShader,
+ HlslShader,
+ DxbcShader, // fxc
+ MslShader,
+ DxilShader, // dxc
+ MetalLibShader // xcrun metal + xcrun metallib
+ };
+
+ enum Variant {
+ StandardShader = 0,
+ BatchableVertexShader
+ };
+
+ QShader();
+ QShader(const QShader &other);
+ QShader &operator=(const QShader &other);
+ ~QShader();
+ void detach();
+
+ bool isValid() const;
+
+ Stage stage() const;
+ void setStage(Stage stage);
+
+ QShaderDescription description() const;
+ void setDescription(const QShaderDescription &desc);
+
+ QVector<QShaderKey> availableShaders() const;
+ QShaderCode shader(const QShaderKey &key) const;
+ void setShader(const QShaderKey &key, const QShaderCode &shader);
+ void removeShader(const QShaderKey &key);
+
+ QByteArray serialized() const;
+ static QShader fromSerialized(const QByteArray &data);
+
+private:
+ QShaderPrivate *d;
+ friend struct QShaderPrivate;
+ friend Q_GUI_EXPORT bool operator==(const QShader &, const QShader &) Q_DECL_NOTHROW;
+ friend Q_GUI_EXPORT uint qHash(const QShader &, uint) Q_DECL_NOTHROW;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &);
+#endif
+};
+
+class Q_GUI_EXPORT QShaderKey
+{
+public:
+ QShaderKey() = default;
+ QShaderKey(QShader::Source s,
+ const QShaderVersion &sver,
+ QShader::Variant svar = QShader::StandardShader);
+
+ QShader::Source source() const { return m_source; }
+ void setSource(QShader::Source s) { m_source = s; }
+
+ QShaderVersion sourceVersion() const { return m_sourceVersion; }
+ void setSourceVersion(const QShaderVersion &sver) { m_sourceVersion = sver; }
+
+ QShader::Variant sourceVariant() const { return m_sourceVariant; }
+ void setSourceVariant(QShader::Variant svar) { m_sourceVariant = svar; }
+
+private:
+ QShader::Source m_source = QShader::SpirvShader;
+ QShaderVersion m_sourceVersion;
+ QShader::Variant m_sourceVariant = QShader::StandardShader;
+};
+
+Q_DECLARE_TYPEINFO(QShaderKey, Q_MOVABLE_TYPE);
+
+Q_GUI_EXPORT bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT uint qHash(const QShader &s, uint seed = 0) Q_DECL_NOTHROW;
+
+inline bool operator!=(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+Q_GUI_EXPORT bool operator==(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator==(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW;
+Q_GUI_EXPORT bool operator==(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW;
+
+inline bool operator!=(const QShaderVersion &lhs, const QShaderVersion &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const QShaderKey &lhs, const QShaderKey &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+inline bool operator!=(const QShaderCode &lhs, const QShaderCode &rhs) Q_DECL_NOTHROW
+{
+ return !(lhs == rhs);
+}
+
+Q_GUI_EXPORT uint qHash(const QShaderKey &k, uint seed = 0) Q_DECL_NOTHROW;
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShader &);
+Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderKey &k);
+Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QShaderVersion &v);
+#endif
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h
new file mode 100644
index 0000000000..6473590e95
--- /dev/null
+++ b/src/gui/rhi/qshader_p_p.h
@@ -0,0 +1,84 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADER_P_P_H
+#define QSHADER_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qshader_p.h"
+#include <QtCore/QAtomicInt>
+#include <QtCore/QHash>
+#include <QtCore/QDebug>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QShaderPrivate
+{
+ QShaderPrivate()
+ : ref(1)
+ {
+ }
+
+ QShaderPrivate(const QShaderPrivate *other)
+ : ref(1),
+ stage(other->stage),
+ desc(other->desc),
+ shaders(other->shaders)
+ {
+ }
+
+ static QShaderPrivate *get(QShader *s) { return s->d; }
+ static const QShaderPrivate *get(const QShader *s) { return s->d; }
+
+ QAtomicInt ref;
+ QShader::Stage stage = QShader::VertexStage;
+ QShaderDescription desc;
+ QHash<QShaderKey, QShaderCode> shaders;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp
new file mode 100644
index 0000000000..77aceaddba
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription.cpp
@@ -0,0 +1,1116 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qshaderdescription_p_p.h"
+#include <QDebug>
+#include <QJsonObject>
+#include <QJsonArray>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QShaderDescription
+ \inmodule QtRhi
+
+ \brief Describes the interface of a shader.
+
+ A shader typically has a set of inputs and outputs. A vertex shader for
+ example has a number of input variables and may use one or more uniform
+ buffers to access data (e.g. a modelview matrix) provided by the
+ application. The shader for the fragment stage receives data from the
+ vertex stage (in a simple setup) and may also rely on data from uniform
+ buffers, images, and samplers.
+
+ When it comes to vertex inputs and the layout of the uniform buffers (what
+ are the names of the members? what is there size, offset, and so on),
+ applications and frameworks may need to discover this dynamically at run
+ time. This is typical when the shader is not built-in but provided by an
+ external entity, like the user.
+
+ Modern and lean graphics APIs may no longer provide a way to query shader
+ reflection information at run time. Therefore, such data is now
+ automatically generated by QShaderBaker and is provided as a
+ QShaderDescription object for each and every QShader.
+
+ \section2 Example
+
+ Take the following vertex shader:
+
+ \badcode
+ #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;
+ } ubuf;
+
+ out gl_PerVertex { vec4 gl_Position; };
+
+ void main()
+ {
+ v_color = color;
+ gl_Position = ubuf.mvp * position;
+ }
+ \endcode
+
+ This shader has two inputs: \c position at location 0 with a type of \c
+ vec4, and \c color at location 1 with a type of \c vec3. It has one output:
+ \c v_color, although this is typically not interesting for applications.
+ What is more important, there is a uniform block at binding 0 with a size
+ of 68 bytes and two members, a 4x4 matrix named \c mvp at offset 0, and a
+ float \c opacity at offset 64.
+
+ All this is described by a QShaderDescription object. QShaderDescription
+ can also be serialized to JSON and binary JSON, and can be deserialized
+ from binary JSON. In practice this is rarely needed since QShader
+ takes care of the associated QShaderDescription automatically, but if the
+ QShaderDescription of the above shader would be written out as JSON, it
+ would look like the following:
+
+ \badcode
+ {
+ "inputs": [
+ {
+ "location": 1,
+ "name": "color",
+ "type": "vec3"
+ },
+ {
+ "location": 0,
+ "name": "position",
+ "type": "vec4"
+ }
+ ],
+ "outputs": [
+ {
+ "location": 0,
+ "name": "v_color",
+ "type": "vec3"
+ }
+ ],
+ "uniformBlocks": [
+ {
+ "binding": 0,
+ "blockName": "buf",
+ "members": [
+ {
+ "matrixStride": 16,
+ "name": "mvp",
+ "offset": 0,
+ "size": 64,
+ "type": "mat4"
+ },
+ {
+ "name": "opacity",
+ "offset": 64,
+ "size": 4,
+ "type": "float"
+ }
+ ],
+ "set": 0,
+ "size": 68,
+ "structName": "ubuf"
+ }
+ ]
+ }
+ \endcode
+
+ The C++ API allows accessing a data structure like the above. For
+ simplicity the inner structs only contain public data members, also
+ considering that their layout is unlikely to change in the future.
+
+ \sa QShaderBaker, QShader
+ */
+
+/*!
+ \enum QShaderDescription::VariableType
+ Represents the type of a variable or block member.
+
+ \value Unknown
+ \value Float
+ \value Vec2
+ \value Vec3
+ \value Vec4
+ \value Mat2
+ \value Mat2x3
+ \value Mat2x4
+ \value Mat3
+ \value Mat3x2
+ \value Mat3x4
+ \value Mat4
+ \value Mat4x2
+ \value Mat4x3
+ \value Int
+ \value Int2
+ \value Int3
+ \value Int4
+ \value Uint
+ \value Uint2
+ \value Uint3
+ \value Uint4
+ \value Bool
+ \value Bool2
+ \value Bool3
+ \value Bool4
+ \value Double
+ \value Double2
+ \value Double3
+ \value Double4
+ \value DMat2
+ \value DMat2x3
+ \value DMat2x4
+ \value DMat3
+ \value DMat3x2
+ \value DMat3x4
+ \value DMat4
+ \value DMat4x2
+ \value DMat4x3
+ \value Sampler1D
+ \value Sampler2D
+ \value Sampler2DMS
+ \value Sampler3D
+ \value SamplerCube
+ \value Sampler1DArray
+ \value Sampler2DArray
+ \value Sampler2DMSArray
+ \value Sampler3DArray
+ \value SamplerCubeArray
+ \value SamplerRect
+ \value SamplerBuffer
+ \value Image1D
+ \value Image2D
+ \value Image2DMS
+ \value Image3D
+ \value ImageCube
+ \value Image1DArray
+ \value Image2DArray
+ \value Image2DMSArray
+ \value Image3DArray
+ \value ImageCubeArray
+ \value ImageRect
+ \value ImageBuffer
+ \value Struct
+ */
+
+/*!
+ \class QShaderDescription::InOutVariable
+ \inmodule QtRhi
+
+ \brief Describes an input or output variable in the shader.
+ */
+
+/*!
+ \class QShaderDescription::BlockVariable
+ \inmodule QtRhi
+
+ \brief Describes a member of a uniform or push constant block.
+ */
+
+/*!
+ \class QShaderDescription::UniformBlock
+ \inmodule QtRhi
+
+ \brief Describes a uniform block.
+
+ \note When translating to shading languages without uniform block support
+ (like GLSL 120 or GLSL/ES 100), uniform blocks are replaced with ordinary
+ uniforms in a struct. The name of the struct, and so the prefix for the
+ uniforms generated from the block members, is given by structName.
+ */
+
+/*!
+ \class QShaderDescription::PushConstantBlock
+ \inmodule QtRhi
+
+ \brief Describes a push constant block.
+ */
+
+/*!
+ \class QShaderDescription::StorageBlock
+ \inmodule QtRhi
+
+ \brief Describes a shader storage block.
+ */
+
+/*!
+ Constructs a new, empty QShaderDescription.
+
+ \note Being empty implies that isValid() returns \c false for the
+ newly constructed instance.
+ */
+QShaderDescription::QShaderDescription()
+ : d(new QShaderDescriptionPrivate)
+{
+}
+
+/*!
+ \internal
+ */
+void QShaderDescription::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ \internal
+ */
+QShaderDescription::QShaderDescription(const QShaderDescription &other)
+ : d(other.d)
+{
+ d->ref.ref();
+}
+
+/*!
+ \internal
+ */
+QShaderDescription &QShaderDescription::operator=(const QShaderDescription &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destructor.
+ */
+QShaderDescription::~QShaderDescription()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \return true if the QShaderDescription contains at least one entry in one of
+ the variable and block lists.
+ */
+bool QShaderDescription::isValid() const
+{
+ return !d->inVars.isEmpty() || !d->outVars.isEmpty()
+ || !d->uniformBlocks.isEmpty() || !d->pushConstantBlocks.isEmpty() || !d->storageBlocks.isEmpty()
+ || !d->combinedImageSamplers.isEmpty() || !d->storageImages.isEmpty();
+}
+
+/*!
+ \return a serialized binary version of the data.
+
+ \sa toJson()
+ */
+QByteArray QShaderDescription::toBinaryJson() const
+{
+ return d->makeDoc().toBinaryData();
+}
+
+/*!
+ \return a serialized JSON text version of the data.
+
+ \note There is no deserialization method provided for JSON text.
+
+ \sa toBinaryJson()
+ */
+QByteArray QShaderDescription::toJson() const
+{
+ return d->makeDoc().toJson();
+}
+
+/*!
+ Deserializes the given binary JSON \a data and returns a new
+ QShaderDescription.
+ */
+QShaderDescription QShaderDescription::fromBinaryJson(const QByteArray &data)
+{
+ QShaderDescription desc;
+ QShaderDescriptionPrivate::get(&desc)->loadDoc(QJsonDocument::fromBinaryData(data));
+ return desc;
+}
+
+/*!
+ \return the list of input variables. This includes vertex inputs (sometimes
+ called attributes) for the vertex stage, and inputs for other stages
+ (sometimes called varyings).
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::inputVariables() const
+{
+ return d->inVars;
+}
+
+/*!
+ \return the list of output variables.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::outputVariables() const
+{
+ return d->outVars;
+}
+
+/*!
+ \return the list of uniform blocks.
+ */
+QVector<QShaderDescription::UniformBlock> QShaderDescription::uniformBlocks() const
+{
+ return d->uniformBlocks;
+}
+
+/*!
+ \return the list of push constant blocks.
+
+ \note Avoid relying on push constant blocks for shaders that are to be used
+ in combination with the Qt Rendering Hardware Interface since that
+ currently has no support for them.
+ */
+QVector<QShaderDescription::PushConstantBlock> QShaderDescription::pushConstantBlocks() const
+{
+ return d->pushConstantBlocks;
+}
+
+/*!
+ \return the list of shader storage blocks.
+
+ For example, with GLSL/Vulkan shaders as source, the declaration
+
+ \badcode
+ struct Stuff {
+ vec2 a;
+ vec2 b;
+ };
+ layout(std140, binding = 0) buffer StuffSsbo {
+ vec4 whatever;
+ Stuff stuff[];
+ } buf;
+ \endcode
+
+ generates the following: (shown as textual JSON here)
+
+ \badcode
+ "storageBlocks": [ {
+ "binding": 0,
+ "blockName": "StuffSsbo",
+ "instanceName": "buf",
+ "knownSize": 16,
+ "members": [
+ {
+ "name": "whatever",
+ "offset": 0,
+ "size": 16,
+ "type": "vec4"
+ },
+ {
+ "arrayDims": [
+ 0
+ ],
+ "name": "stuff",
+ "offset": 16,
+ "size": 0,
+ "structMembers": [
+ {
+ "name": "a",
+ "offset": 0,
+ "size": 8,
+ "type": "vec2"
+ },
+ {
+ "name": "b",
+ "offset": 8,
+ "size": 8,
+ "type": "vec2"
+ }
+ ],
+ "type": "struct"
+ }
+ ],
+ "set": 0
+ } ]
+ \endcode
+
+ \note The size of the last member in the storage block is undefined. This shows
+ up as \c size 0 and an array dimension of \c{[0]}. The storage block's \c knownSize
+ excludes the size of the last member since that will only be known at run time.
+
+ \note SSBOs are not available with some graphics APIs, such as, OpenGL 2.x or
+ OpenGL ES older than 3.1.
+ */
+QVector<QShaderDescription::StorageBlock> QShaderDescription::storageBlocks() const
+{
+ return d->storageBlocks;
+}
+
+/*!
+ \return the list of combined image samplers
+
+ With GLSL/Vulkan shaders as source a \c{layout(binding = 1) uniform sampler2D tex;}
+ uniform generates the following: (shown as textual JSON here)
+
+ \badcode
+ "combinedImageSamplers": [
+ {
+ "binding": 1,
+ "name": "tex",
+ "set": 0,
+ "type": "sampler2D"
+ }
+ ]
+ \endcode
+
+ This does not mean that other language versions of the shader must also use
+ a combined image sampler, especially considering that the concept may not
+ exist everywhere. For instance, a HLSL version will likely just use a
+ Texture2D and SamplerState object with registers t1 and s1, respectively.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::combinedImageSamplers() const
+{
+ return d->combinedImageSamplers;
+}
+
+/*!
+ \return the list of image variables.
+
+ These will likely occur in compute shaders. For example,
+ \c{layout (binding = 0, rgba8) uniform readonly image2D inputImage;}
+ generates the following: (shown as textual JSON here)
+
+ \badcode
+ "storageImages": [
+ {
+ "binding": 0,
+ "imageFormat": "rgba8",
+ "name": "inputImage",
+ "set": 0,
+ "type": "image2D"
+ }
+ ]
+ \endcode
+
+ \note Separate image objects are not compatible with some graphics APIs,
+ such as, OpenGL 2.x or OpenGL ES older than 3.1.
+ */
+QVector<QShaderDescription::InOutVariable> QShaderDescription::storageImages() const
+{
+ return d->storageImages;
+}
+
+/*!
+ Returns the local size of a compute shader.
+
+ For example, for a compute shader with the following declaration the
+ function returns { 256, 16, 1}.
+
+ \badcode
+ layout(local_size_x = 256, local_size_y = 16, local_size_z = 1) in;
+ \endcode
+ */
+std::array<uint, 3> QShaderDescription::computeShaderLocalSize() const
+{
+ return d->localSize;
+}
+
+static struct TypeTab {
+ QString k;
+ QShaderDescription::VariableType v;
+} typeTab[] = {
+ { QLatin1String("float"), QShaderDescription::Float },
+ { QLatin1String("vec2"), QShaderDescription::Vec2 },
+ { QLatin1String("vec3"), QShaderDescription::Vec3 },
+ { QLatin1String("vec4"), QShaderDescription::Vec4 },
+ { QLatin1String("mat2"), QShaderDescription::Mat2 },
+ { QLatin1String("mat3"), QShaderDescription::Mat3 },
+ { QLatin1String("mat4"), QShaderDescription::Mat4 },
+
+ { QLatin1String("struct"), QShaderDescription::Struct },
+
+ { QLatin1String("sampler1D"), QShaderDescription::Sampler1D },
+ { QLatin1String("sampler2D"), QShaderDescription::Sampler2D },
+ { QLatin1String("sampler2DMS"), QShaderDescription::Sampler2DMS },
+ { QLatin1String("sampler3D"), QShaderDescription::Sampler3D },
+ { QLatin1String("samplerCube"), QShaderDescription::SamplerCube },
+ { QLatin1String("sampler1DArray"), QShaderDescription::Sampler1DArray },
+ { QLatin1String("sampler2DArray"), QShaderDescription::Sampler2DArray },
+ { QLatin1String("sampler2DMSArray"), QShaderDescription::Sampler2DMSArray },
+ { QLatin1String("sampler3DArray"), QShaderDescription::Sampler3DArray },
+ { QLatin1String("samplerCubeArray"), QShaderDescription::SamplerCubeArray },
+ { QLatin1String("samplerRect"), QShaderDescription::SamplerRect },
+ { QLatin1String("samplerBuffer"), QShaderDescription::SamplerBuffer },
+
+ { QLatin1String("mat2x3"), QShaderDescription::Mat2x3 },
+ { QLatin1String("mat2x4"), QShaderDescription::Mat2x4 },
+ { QLatin1String("mat3x2"), QShaderDescription::Mat3x2 },
+ { QLatin1String("mat3x4"), QShaderDescription::Mat3x4 },
+ { QLatin1String("mat4x2"), QShaderDescription::Mat4x2 },
+ { QLatin1String("mat4x3"), QShaderDescription::Mat4x3 },
+
+ { QLatin1String("int"), QShaderDescription::Int },
+ { QLatin1String("ivec2"), QShaderDescription::Int2 },
+ { QLatin1String("ivec3"), QShaderDescription::Int3 },
+ { QLatin1String("ivec4"), QShaderDescription::Int4 },
+
+ { QLatin1String("uint"), QShaderDescription::Uint },
+ { QLatin1String("uvec2"), QShaderDescription::Uint2 },
+ { QLatin1String("uvec3"), QShaderDescription::Uint3 },
+ { QLatin1String("uvec4"), QShaderDescription::Uint4 },
+
+ { QLatin1String("bool"), QShaderDescription::Bool },
+ { QLatin1String("bvec2"), QShaderDescription::Bool2 },
+ { QLatin1String("bvec3"), QShaderDescription::Bool3 },
+ { QLatin1String("bvec4"), QShaderDescription::Bool4 },
+
+ { QLatin1String("double"), QShaderDescription::Double },
+ { QLatin1String("dvec2"), QShaderDescription::Double2 },
+ { QLatin1String("dvec3"), QShaderDescription::Double3 },
+ { QLatin1String("dvec4"), QShaderDescription::Double4 },
+ { QLatin1String("dmat2"), QShaderDescription::DMat2 },
+ { QLatin1String("dmat3"), QShaderDescription::DMat3 },
+ { QLatin1String("dmat4"), QShaderDescription::DMat4 },
+ { QLatin1String("dmat2x3"), QShaderDescription::DMat2x3 },
+ { QLatin1String("dmat2x4"), QShaderDescription::DMat2x4 },
+ { QLatin1String("dmat3x2"), QShaderDescription::DMat3x2 },
+ { QLatin1String("dmat3x4"), QShaderDescription::DMat3x4 },
+ { QLatin1String("dmat4x2"), QShaderDescription::DMat4x2 },
+ { QLatin1String("dmat4x3"), QShaderDescription::DMat4x3 },
+
+ { QLatin1String("image1D"), QShaderDescription::Image1D },
+ { QLatin1String("image2D"), QShaderDescription::Image2D },
+ { QLatin1String("image2DMS"), QShaderDescription::Image2DMS },
+ { QLatin1String("image3D"), QShaderDescription::Image3D },
+ { QLatin1String("imageCube"), QShaderDescription::ImageCube },
+ { QLatin1String("image1DArray"), QShaderDescription::Image1DArray },
+ { QLatin1String("image2DArray"), QShaderDescription::Image2DArray },
+ { QLatin1String("image2DMSArray"), QShaderDescription::Image2DMSArray },
+ { QLatin1String("image3DArray"), QShaderDescription::Image3DArray },
+ { QLatin1String("imageCubeArray"), QShaderDescription::ImageCubeArray },
+ { QLatin1String("imageRect"), QShaderDescription::ImageRect },
+ { QLatin1String("imageBuffer"), QShaderDescription::ImageBuffer }
+};
+
+static QString typeStr(const QShaderDescription::VariableType &t)
+{
+ for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
+ if (typeTab[i].v == t)
+ return typeTab[i].k;
+ }
+ return QString();
+}
+
+static QShaderDescription::VariableType mapType(const QString &t)
+{
+ for (size_t i = 0; i < sizeof(typeTab) / sizeof(TypeTab); ++i) {
+ if (typeTab[i].k == t)
+ return typeTab[i].v;
+ }
+ return QShaderDescription::Unknown;
+}
+
+static struct ImageFormatTab {
+ QString k;
+ QShaderDescription::ImageFormat v;
+} imageFormatTab[] {
+ { QLatin1String("unknown"), QShaderDescription::ImageFormatUnknown },
+ { QLatin1String("rgba32f"), QShaderDescription::ImageFormatRgba32f },
+ { QLatin1String("rgba16"), QShaderDescription::ImageFormatRgba16f },
+ { QLatin1String("r32f"), QShaderDescription::ImageFormatR32f },
+ { QLatin1String("rgba8"), QShaderDescription::ImageFormatRgba8 },
+ { QLatin1String("rgba8_snorm"), QShaderDescription::ImageFormatRgba8Snorm },
+ { QLatin1String("rg32f"), QShaderDescription::ImageFormatRg32f },
+ { QLatin1String("rg16f"), QShaderDescription::ImageFormatRg16f },
+ { QLatin1String("r11f_g11f_b10f"), QShaderDescription::ImageFormatR11fG11fB10f },
+ { QLatin1String("r16f"), QShaderDescription::ImageFormatR16f },
+ { QLatin1String("rgba16"), QShaderDescription::ImageFormatRgba16 },
+ { QLatin1String("rgb10_a2"), QShaderDescription::ImageFormatRgb10A2 },
+ { QLatin1String("rg16"), QShaderDescription::ImageFormatRg16 },
+ { QLatin1String("rg8"), QShaderDescription::ImageFormatRg8 },
+ { QLatin1String("r16"), QShaderDescription::ImageFormatR16 },
+ { QLatin1String("r8"), QShaderDescription::ImageFormatR8 },
+ { QLatin1String("rgba16_snorm"), QShaderDescription::ImageFormatRgba16Snorm },
+ { QLatin1String("rg16_snorm"), QShaderDescription::ImageFormatRg16Snorm },
+ { QLatin1String("rg8_snorm"), QShaderDescription::ImageFormatRg8Snorm },
+ { QLatin1String("r16_snorm"), QShaderDescription::ImageFormatR16Snorm },
+ { QLatin1String("r8_snorm"), QShaderDescription::ImageFormatR8Snorm },
+ { QLatin1String("rgba32i"), QShaderDescription::ImageFormatRgba32i },
+ { QLatin1String("rgba16i"), QShaderDescription::ImageFormatRgba16i },
+ { QLatin1String("rgba8i"), QShaderDescription::ImageFormatRgba8i },
+ { QLatin1String("r32i"), QShaderDescription::ImageFormatR32i },
+ { QLatin1String("rg32i"), QShaderDescription::ImageFormatRg32i },
+ { QLatin1String("rg16i"), QShaderDescription::ImageFormatRg16i },
+ { QLatin1String("rg8i"), QShaderDescription::ImageFormatRg8i },
+ { QLatin1String("r16i"), QShaderDescription::ImageFormatR16i },
+ { QLatin1String("r8i"), QShaderDescription::ImageFormatR8i },
+ { QLatin1String("rgba32ui"), QShaderDescription::ImageFormatRgba32ui },
+ { QLatin1String("rgba16ui"), QShaderDescription::ImageFormatRgba16ui },
+ { QLatin1String("rgba8ui"), QShaderDescription::ImageFormatRgba8ui },
+ { QLatin1String("r32ui"), QShaderDescription::ImageFormatR32ui },
+ { QLatin1String("rgb10_a2ui"), QShaderDescription::ImageFormatRgb10a2ui },
+ { QLatin1String("rg32ui"), QShaderDescription::ImageFormatRg32ui },
+ { QLatin1String("rg16ui"), QShaderDescription::ImageFormatRg16ui },
+ { QLatin1String("rg8ui"), QShaderDescription::ImageFormatRg8ui },
+ { QLatin1String("r16ui"), QShaderDescription::ImageFormatR16ui },
+ { QLatin1String("r8ui"), QShaderDescription::ImageFormatR8ui }
+};
+
+static QString imageFormatStr(const QShaderDescription::ImageFormat &f)
+{
+ for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
+ if (imageFormatTab[i].v == f)
+ return imageFormatTab[i].k;
+ }
+ return QString();
+}
+
+static QShaderDescription::ImageFormat mapImageFormat(const QString &f)
+{
+ for (size_t i = 0; i < sizeof(imageFormatTab) / sizeof(ImageFormatTab); ++i) {
+ if (imageFormatTab[i].k == f)
+ return imageFormatTab[i].v;
+ }
+ return QShaderDescription::ImageFormatUnknown;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QShaderDescription &sd)
+{
+ const QShaderDescriptionPrivate *d = sd.d;
+ QDebugStateSaver saver(dbg);
+
+ if (sd.isValid()) {
+ dbg.nospace() << "QShaderDescription("
+ << "inVars " << d->inVars
+ << " outVars " << d->outVars
+ << " uniformBlocks " << d->uniformBlocks
+ << " pcBlocks " << d->pushConstantBlocks
+ << " storageBlocks " << d->storageBlocks
+ << " combinedSamplers " << d->combinedImageSamplers
+ << " images " << d->storageImages
+ << ')';
+ } else {
+ dbg.nospace() << "QShaderDescription(null)";
+ }
+
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::InOutVariable &var)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "InOutVariable(" << typeStr(var.type) << ' ' << var.name;
+ if (var.location >= 0)
+ dbg.nospace() << " location=" << var.location;
+ if (var.binding >= 0)
+ dbg.nospace() << " binding=" << var.binding;
+ if (var.descriptorSet >= 0)
+ dbg.nospace() << " set=" << var.descriptorSet;
+ if (var.imageFormat != QShaderDescription::ImageFormatUnknown)
+ dbg.nospace() << " imageFormat=" << imageFormatStr(var.imageFormat);
+ if (var.imageFlags)
+ dbg.nospace() << " imageFlags=" << var.imageFlags;
+ dbg.nospace() << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::BlockVariable &var)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "BlockVariable(" << typeStr(var.type) << ' ' << var.name
+ << " offset=" << var.offset << " size=" << var.size;
+ if (!var.arrayDims.isEmpty())
+ dbg.nospace() << " array=" << var.arrayDims;
+ if (var.arrayStride)
+ dbg.nospace() << " arrayStride=" << var.arrayStride;
+ if (var.matrixStride)
+ dbg.nospace() << " matrixStride=" << var.matrixStride;
+ if (var.matrixIsRowMajor)
+ dbg.nospace() << " [rowmaj]";
+ if (!var.structMembers.isEmpty())
+ dbg.nospace() << " structMembers=" << var.structMembers;
+ dbg.nospace() << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::UniformBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "UniformBlock(" << blk.blockName << ' ' << blk.structName << " size=" << blk.size;
+ if (blk.binding >= 0)
+ dbg.nospace() << " binding=" << blk.binding;
+ if (blk.descriptorSet >= 0)
+ dbg.nospace() << " set=" << blk.descriptorSet;
+ dbg.nospace() << ' ' << blk.members << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::PushConstantBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "PushConstantBlock(" << blk.name << " size=" << blk.size << ' ' << blk.members << ')';
+ return dbg;
+}
+
+QDebug operator<<(QDebug dbg, const QShaderDescription::StorageBlock &blk)
+{
+ QDebugStateSaver saver(dbg);
+ dbg.nospace() << "StorageBlock(" << blk.blockName << ' ' << blk.instanceName << " knownSize=" << blk.knownSize;
+ if (blk.binding >= 0)
+ dbg.nospace() << " binding=" << blk.binding;
+ if (blk.descriptorSet >= 0)
+ dbg.nospace() << " set=" << blk.descriptorSet;
+ dbg.nospace() << ' ' << blk.members << ')';
+ return dbg;
+}
+#endif
+
+static const QString nameKey = QLatin1String("name");
+static const QString typeKey = QLatin1String("type");
+static const QString locationKey = QLatin1String("location");
+static const QString bindingKey = QLatin1String("binding");
+static const QString setKey = QLatin1String("set");
+static const QString imageFormatKey = QLatin1String("imageFormat");
+static const QString imageFlagsKey = QLatin1String("imageFlags");
+static const QString offsetKey = QLatin1String("offset");
+static const QString arrayDimsKey = QLatin1String("arrayDims");
+static const QString arrayStrideKey = QLatin1String("arrayStride");
+static const QString matrixStrideKey = QLatin1String("matrixStride");
+static const QString matrixRowMajorKey = QLatin1String("matrixRowMajor");
+static const QString structMembersKey = QLatin1String("structMembers");
+static const QString membersKey = QLatin1String("members");
+static const QString inputsKey = QLatin1String("inputs");
+static const QString outputsKey = QLatin1String("outputs");
+static const QString uniformBlocksKey = QLatin1String("uniformBlocks");
+static const QString blockNameKey = QLatin1String("blockName");
+static const QString structNameKey = QLatin1String("structName");
+static const QString instanceNameKey = QLatin1String("instanceName");
+static const QString sizeKey = QLatin1String("size");
+static const QString knownSizeKey = QLatin1String("knownSize");
+static const QString pushConstantBlocksKey = QLatin1String("pushConstantBlocks");
+static const QString storageBlocksKey = QLatin1String("storageBlocks");
+static const QString combinedImageSamplersKey = QLatin1String("combinedImageSamplers");
+static const QString storageImagesKey = QLatin1String("storageImages");
+static const QString localSizeKey = QLatin1String("localSize");
+
+static void addDeco(QJsonObject *obj, const QShaderDescription::InOutVariable &v)
+{
+ if (v.location >= 0)
+ (*obj)[locationKey] = v.location;
+ if (v.binding >= 0)
+ (*obj)[bindingKey] = v.binding;
+ if (v.descriptorSet >= 0)
+ (*obj)[setKey] = v.descriptorSet;
+ if (v.imageFormat != QShaderDescription::ImageFormatUnknown)
+ (*obj)[imageFormatKey] = imageFormatStr(v.imageFormat);
+ if (v.imageFlags)
+ (*obj)[imageFlagsKey] = int(v.imageFlags);
+}
+
+static QJsonObject inOutObject(const QShaderDescription::InOutVariable &v)
+{
+ QJsonObject obj;
+ obj[nameKey] = v.name;
+ obj[typeKey] = typeStr(v.type);
+ addDeco(&obj, v);
+ return obj;
+}
+
+static QJsonObject blockMemberObject(const QShaderDescription::BlockVariable &v)
+{
+ QJsonObject obj;
+ obj[nameKey] = v.name;
+ obj[typeKey] = typeStr(v.type);
+ obj[offsetKey] = v.offset;
+ obj[sizeKey] = v.size;
+ if (!v.arrayDims.isEmpty()) {
+ QJsonArray dimArr;
+ for (int dim : v.arrayDims)
+ dimArr.append(dim);
+ obj[arrayDimsKey] = dimArr;
+ }
+ if (v.arrayStride)
+ obj[arrayStrideKey] = v.arrayStride;
+ if (v.matrixStride)
+ obj[matrixStrideKey] = v.matrixStride;
+ if (v.matrixIsRowMajor)
+ obj[matrixRowMajorKey] = true;
+ if (!v.structMembers.isEmpty()) {
+ QJsonArray arr;
+ for (const QShaderDescription::BlockVariable &sv : v.structMembers)
+ arr.append(blockMemberObject(sv));
+ obj[structMembersKey] = arr;
+ }
+ return obj;
+}
+
+QJsonDocument QShaderDescriptionPrivate::makeDoc()
+{
+ QJsonObject root;
+
+ QJsonArray jinputs;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(inVars))
+ jinputs.append(inOutObject(v));
+ if (!jinputs.isEmpty())
+ root[inputsKey] = jinputs;
+
+ QJsonArray joutputs;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(outVars))
+ joutputs.append(inOutObject(v));
+ if (!joutputs.isEmpty())
+ root[outputsKey] = joutputs;
+
+ QJsonArray juniformBlocks;
+ for (const QShaderDescription::UniformBlock &b : uniformBlocks) {
+ QJsonObject juniformBlock;
+ juniformBlock[blockNameKey] = b.blockName;
+ juniformBlock[structNameKey] = b.structName;
+ juniformBlock[sizeKey] = b.size;
+ if (b.binding >= 0)
+ juniformBlock[bindingKey] = b.binding;
+ if (b.descriptorSet >= 0)
+ juniformBlock[setKey] = b.descriptorSet;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ juniformBlock[membersKey] = members;
+ juniformBlocks.append(juniformBlock);
+ }
+ if (!juniformBlocks.isEmpty())
+ root[uniformBlocksKey] = juniformBlocks;
+
+ QJsonArray jpushConstantBlocks;
+ for (const QShaderDescription::PushConstantBlock &b : pushConstantBlocks) {
+ QJsonObject jpushConstantBlock;
+ jpushConstantBlock[nameKey] = b.name;
+ jpushConstantBlock[sizeKey] = b.size;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ jpushConstantBlock[membersKey] = members;
+ jpushConstantBlocks.append(jpushConstantBlock);
+ }
+ if (!jpushConstantBlocks.isEmpty())
+ root[pushConstantBlocksKey] = jpushConstantBlocks;
+
+ QJsonArray jstorageBlocks;
+ for (const QShaderDescription::StorageBlock &b : storageBlocks) {
+ QJsonObject jstorageBlock;
+ jstorageBlock[blockNameKey] = b.blockName;
+ jstorageBlock[instanceNameKey] = b.instanceName;
+ jstorageBlock[knownSizeKey] = b.knownSize;
+ if (b.binding >= 0)
+ jstorageBlock[bindingKey] = b.binding;
+ if (b.descriptorSet >= 0)
+ jstorageBlock[setKey] = b.descriptorSet;
+ QJsonArray members;
+ for (const QShaderDescription::BlockVariable &v : b.members)
+ members.append(blockMemberObject(v));
+ jstorageBlock[membersKey] = members;
+ jstorageBlocks.append(jstorageBlock);
+ }
+ if (!jstorageBlocks.isEmpty())
+ root[storageBlocksKey] = jstorageBlocks;
+
+ QJsonArray jcombinedSamplers;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(combinedImageSamplers)) {
+ QJsonObject sampler;
+ sampler[nameKey] = v.name;
+ sampler[typeKey] = typeStr(v.type);
+ addDeco(&sampler, v);
+ jcombinedSamplers.append(sampler);
+ }
+ if (!jcombinedSamplers.isEmpty())
+ root[combinedImageSamplersKey] = jcombinedSamplers;
+
+ QJsonArray jstorageImages;
+ for (const QShaderDescription::InOutVariable &v : qAsConst(storageImages)) {
+ QJsonObject image;
+ image[nameKey] = v.name;
+ image[typeKey] = typeStr(v.type);
+ addDeco(&image, v);
+ jstorageImages.append(image);
+ }
+ if (!jstorageImages.isEmpty())
+ root[storageImagesKey] = jstorageImages;
+
+ QJsonArray jlocalSize;
+ for (int i = 0; i < 3; ++i)
+ jlocalSize.append(QJsonValue(int(localSize[i])));
+ root[localSizeKey] = jlocalSize;
+
+ return QJsonDocument(root);
+}
+
+static QShaderDescription::InOutVariable inOutVar(const QJsonObject &obj)
+{
+ QShaderDescription::InOutVariable var;
+ var.name = obj[nameKey].toString();
+ var.type = mapType(obj[typeKey].toString());
+ if (obj.contains(locationKey))
+ var.location = obj[locationKey].toInt();
+ if (obj.contains(bindingKey))
+ var.binding = obj[bindingKey].toInt();
+ if (obj.contains(setKey))
+ var.descriptorSet = obj[setKey].toInt();
+ if (obj.contains(imageFormatKey))
+ var.imageFormat = mapImageFormat(obj[imageFormatKey].toString());
+ if (obj.contains(imageFlagsKey))
+ var.imageFlags = QShaderDescription::ImageFlags(obj[imageFlagsKey].toInt());
+ return var;
+}
+
+static QShaderDescription::BlockVariable blockVar(const QJsonObject &obj)
+{
+ QShaderDescription::BlockVariable var;
+ var.name = obj[nameKey].toString();
+ var.type = mapType(obj[typeKey].toString());
+ var.offset = obj[offsetKey].toInt();
+ var.size = obj[sizeKey].toInt();
+ if (obj.contains(arrayDimsKey)) {
+ QJsonArray dimArr = obj[arrayDimsKey].toArray();
+ for (int i = 0; i < dimArr.count(); ++i)
+ var.arrayDims.append(dimArr.at(i).toInt());
+ }
+ if (obj.contains(arrayStrideKey))
+ var.arrayStride = obj[arrayStrideKey].toInt();
+ if (obj.contains(matrixStrideKey))
+ var.matrixStride = obj[matrixStrideKey].toInt();
+ if (obj.contains(matrixRowMajorKey))
+ var.matrixIsRowMajor = obj[matrixRowMajorKey].toBool();
+ if (obj.contains(structMembersKey)) {
+ QJsonArray arr = obj[structMembersKey].toArray();
+ for (int i = 0; i < arr.count(); ++i)
+ var.structMembers.append(blockVar(arr.at(i).toObject()));
+ }
+ return var;
+}
+
+void QShaderDescriptionPrivate::loadDoc(const QJsonDocument &doc)
+{
+ if (doc.isNull()) {
+ qWarning("QShaderDescription: JSON document is empty");
+ return;
+ }
+
+ Q_ASSERT(ref.load() == 1); // must be detached
+
+ inVars.clear();
+ outVars.clear();
+ uniformBlocks.clear();
+ pushConstantBlocks.clear();
+ storageBlocks.clear();
+ combinedImageSamplers.clear();
+ storageImages.clear();
+
+ QJsonObject root = doc.object();
+
+ if (root.contains(inputsKey)) {
+ QJsonArray inputs = root[inputsKey].toArray();
+ for (int i = 0; i < inputs.count(); ++i)
+ inVars.append(inOutVar(inputs[i].toObject()));
+ }
+
+ if (root.contains(outputsKey)) {
+ QJsonArray outputs = root[outputsKey].toArray();
+ for (int i = 0; i < outputs.count(); ++i)
+ outVars.append(inOutVar(outputs[i].toObject()));
+ }
+
+ if (root.contains(uniformBlocksKey)) {
+ QJsonArray ubs = root[uniformBlocksKey].toArray();
+ for (int i = 0; i < ubs.count(); ++i) {
+ QJsonObject ubObj = ubs[i].toObject();
+ QShaderDescription::UniformBlock ub;
+ ub.blockName = ubObj[blockNameKey].toString();
+ ub.structName = ubObj[structNameKey].toString();
+ ub.size = ubObj[sizeKey].toInt();
+ if (ubObj.contains(bindingKey))
+ ub.binding = ubObj[bindingKey].toInt();
+ if (ubObj.contains(setKey))
+ ub.descriptorSet = ubObj[setKey].toInt();
+ QJsonArray members = ubObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ ub.members.append(blockVar(member.toObject()));
+ uniformBlocks.append(ub);
+ }
+ }
+
+ if (root.contains(pushConstantBlocksKey)) {
+ QJsonArray pcs = root[pushConstantBlocksKey].toArray();
+ for (int i = 0; i < pcs.count(); ++i) {
+ QJsonObject pcObj = pcs[i].toObject();
+ QShaderDescription::PushConstantBlock pc;
+ pc.name = pcObj[nameKey].toString();
+ pc.size = pcObj[sizeKey].toInt();
+ QJsonArray members = pcObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ pc.members.append(blockVar(member.toObject()));
+ pushConstantBlocks.append(pc);
+ }
+ }
+
+ if (root.contains(storageBlocksKey)) {
+ QJsonArray ubs = root[storageBlocksKey].toArray();
+ for (int i = 0; i < ubs.count(); ++i) {
+ QJsonObject sbObj = ubs[i].toObject();
+ QShaderDescription::StorageBlock sb;
+ sb.blockName = sbObj[blockNameKey].toString();
+ sb.instanceName = sbObj[instanceNameKey].toString();
+ sb.knownSize = sbObj[knownSizeKey].toInt();
+ if (sbObj.contains(bindingKey))
+ sb.binding = sbObj[bindingKey].toInt();
+ if (sbObj.contains(setKey))
+ sb.descriptorSet = sbObj[setKey].toInt();
+ QJsonArray members = sbObj[membersKey].toArray();
+ for (const QJsonValue &member : members)
+ sb.members.append(blockVar(member.toObject()));
+ storageBlocks.append(sb);
+ }
+ }
+
+ if (root.contains(combinedImageSamplersKey)) {
+ QJsonArray samplers = root[combinedImageSamplersKey].toArray();
+ for (int i = 0; i < samplers.count(); ++i)
+ combinedImageSamplers.append(inOutVar(samplers[i].toObject()));
+ }
+
+ if (root.contains(storageImagesKey)) {
+ QJsonArray images = root[storageImagesKey].toArray();
+ for (int i = 0; i < images.count(); ++i)
+ storageImages.append(inOutVar(images[i].toObject()));
+ }
+
+ if (root.contains(localSizeKey)) {
+ QJsonArray localSizeArr = root[localSizeKey].toArray();
+ if (localSizeArr.count() == 3) {
+ for (int i = 0; i < 3; ++i)
+ localSize[i] = localSizeArr[i].toInt();
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qshaderdescription_p.h b/src/gui/rhi/qshaderdescription_p.h
new file mode 100644
index 0000000000..5a63b998cd
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription_p.h
@@ -0,0 +1,281 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADERDESCRIPTION_H
+#define QSHADERDESCRIPTION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <QtCore/QString>
+#include <QtCore/QVector>
+#include <array>
+
+QT_BEGIN_NAMESPACE
+
+struct QShaderDescriptionPrivate;
+
+class Q_GUI_EXPORT QShaderDescription
+{
+public:
+ QShaderDescription();
+ QShaderDescription(const QShaderDescription &other);
+ QShaderDescription &operator=(const QShaderDescription &other);
+ ~QShaderDescription();
+ void detach();
+
+ bool isValid() const;
+
+ QByteArray toBinaryJson() const;
+ QByteArray toJson() const;
+
+ static QShaderDescription fromBinaryJson(const QByteArray &data);
+
+ enum VariableType {
+ Unknown = 0,
+
+ // do not reorder
+ Float,
+ Vec2,
+ Vec3,
+ Vec4,
+ Mat2,
+ Mat2x3,
+ Mat2x4,
+ Mat3,
+ Mat3x2,
+ Mat3x4,
+ Mat4,
+ Mat4x2,
+ Mat4x3,
+
+ Int,
+ Int2,
+ Int3,
+ Int4,
+
+ Uint,
+ Uint2,
+ Uint3,
+ Uint4,
+
+ Bool,
+ Bool2,
+ Bool3,
+ Bool4,
+
+ Double,
+ Double2,
+ Double3,
+ Double4,
+ DMat2,
+ DMat2x3,
+ DMat2x4,
+ DMat3,
+ DMat3x2,
+ DMat3x4,
+ DMat4,
+ DMat4x2,
+ DMat4x3,
+
+ Sampler1D,
+ Sampler2D,
+ Sampler2DMS,
+ Sampler3D,
+ SamplerCube,
+ Sampler1DArray,
+ Sampler2DArray,
+ Sampler2DMSArray,
+ Sampler3DArray,
+ SamplerCubeArray,
+ SamplerRect,
+ SamplerBuffer,
+
+ Image1D,
+ Image2D,
+ Image2DMS,
+ Image3D,
+ ImageCube,
+ Image1DArray,
+ Image2DArray,
+ Image2DMSArray,
+ Image3DArray,
+ ImageCubeArray,
+ ImageRect,
+ ImageBuffer,
+
+ Struct
+ };
+
+ enum ImageFormat {
+ // must match SPIR-V's ImageFormat
+ ImageFormatUnknown = 0,
+ ImageFormatRgba32f = 1,
+ ImageFormatRgba16f = 2,
+ ImageFormatR32f = 3,
+ ImageFormatRgba8 = 4,
+ ImageFormatRgba8Snorm = 5,
+ ImageFormatRg32f = 6,
+ ImageFormatRg16f = 7,
+ ImageFormatR11fG11fB10f = 8,
+ ImageFormatR16f = 9,
+ ImageFormatRgba16 = 10,
+ ImageFormatRgb10A2 = 11,
+ ImageFormatRg16 = 12,
+ ImageFormatRg8 = 13,
+ ImageFormatR16 = 14,
+ ImageFormatR8 = 15,
+ ImageFormatRgba16Snorm = 16,
+ ImageFormatRg16Snorm = 17,
+ ImageFormatRg8Snorm = 18,
+ ImageFormatR16Snorm = 19,
+ ImageFormatR8Snorm = 20,
+ ImageFormatRgba32i = 21,
+ ImageFormatRgba16i = 22,
+ ImageFormatRgba8i = 23,
+ ImageFormatR32i = 24,
+ ImageFormatRg32i = 25,
+ ImageFormatRg16i = 26,
+ ImageFormatRg8i = 27,
+ ImageFormatR16i = 28,
+ ImageFormatR8i = 29,
+ ImageFormatRgba32ui = 30,
+ ImageFormatRgba16ui = 31,
+ ImageFormatRgba8ui = 32,
+ ImageFormatR32ui = 33,
+ ImageFormatRgb10a2ui = 34,
+ ImageFormatRg32ui = 35,
+ ImageFormatRg16ui = 36,
+ ImageFormatRg8ui = 37,
+ ImageFormatR16ui = 38,
+ ImageFormatR8ui = 39
+ };
+
+ enum ImageFlag {
+ ReadOnlyImage = 1 << 0,
+ WriteOnlyImage = 1 << 1
+ };
+ Q_DECLARE_FLAGS(ImageFlags, ImageFlag)
+
+ // Optional data (like decorations) usually default to an otherwise invalid value (-1 or 0). This is intentional.
+
+ struct InOutVariable {
+ QString name;
+ VariableType type = Unknown;
+ int location = -1;
+ int binding = -1;
+ int descriptorSet = -1;
+ ImageFormat imageFormat = ImageFormatUnknown;
+ ImageFlags imageFlags;
+ };
+
+ struct BlockVariable {
+ QString name;
+ VariableType type = Unknown;
+ int offset = 0;
+ int size = 0;
+ QVector<int> arrayDims;
+ int arrayStride = 0;
+ int matrixStride = 0;
+ bool matrixIsRowMajor = false;
+ QVector<BlockVariable> structMembers;
+ };
+
+ struct UniformBlock {
+ QString blockName;
+ QString structName; // instanceName
+ int size = 0;
+ int binding = -1;
+ int descriptorSet = -1;
+ QVector<BlockVariable> members;
+ };
+
+ struct PushConstantBlock {
+ QString name;
+ int size = 0;
+ QVector<BlockVariable> members;
+ };
+
+ struct StorageBlock {
+ QString blockName;
+ QString instanceName;
+ int knownSize = 0;
+ int binding = -1;
+ int descriptorSet = -1;
+ QVector<BlockVariable> members;
+ };
+
+ QVector<InOutVariable> inputVariables() const;
+ QVector<InOutVariable> outputVariables() const;
+ QVector<UniformBlock> uniformBlocks() const;
+ QVector<PushConstantBlock> pushConstantBlocks() const;
+ QVector<StorageBlock> storageBlocks() const;
+ QVector<InOutVariable> combinedImageSamplers() const;
+ QVector<InOutVariable> storageImages() const;
+
+ std::array<uint, 3> computeShaderLocalSize() const;
+
+private:
+ QShaderDescriptionPrivate *d;
+ friend struct QShaderDescriptionPrivate;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &);
+#endif
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QShaderDescription::ImageFlags)
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::InOutVariable &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::BlockVariable &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::UniformBlock &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::PushConstantBlock &);
+Q_GUI_EXPORT QDebug operator<<(QDebug, const QShaderDescription::StorageBlock &);
+#endif
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qshaderdescription_p_p.h b/src/gui/rhi/qshaderdescription_p_p.h
new file mode 100644
index 0000000000..1caee24984
--- /dev/null
+++ b/src/gui/rhi/qshaderdescription_p_p.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Gui module
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSHADERDESCRIPTION_P_H
+#define QSHADERDESCRIPTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of a number of Qt sources files. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qshaderdescription_p.h"
+#include <QtCore/QVector>
+#include <QtCore/QAtomicInt>
+#include <QtCore/QJsonDocument>
+
+QT_BEGIN_NAMESPACE
+
+struct Q_GUI_EXPORT QShaderDescriptionPrivate
+{
+ QShaderDescriptionPrivate()
+ : ref(1)
+ {
+ localSize[0] = localSize[1] = localSize[2] = 0;
+ }
+
+ QShaderDescriptionPrivate(const QShaderDescriptionPrivate *other)
+ : ref(1),
+ inVars(other->inVars),
+ outVars(other->outVars),
+ uniformBlocks(other->uniformBlocks),
+ pushConstantBlocks(other->pushConstantBlocks),
+ storageBlocks(other->storageBlocks),
+ combinedImageSamplers(other->combinedImageSamplers),
+ storageImages(other->storageImages),
+ localSize(other->localSize)
+ {
+ }
+
+ static QShaderDescriptionPrivate *get(QShaderDescription *desc) { return desc->d; }
+ static const QShaderDescriptionPrivate *get(const QShaderDescription *desc) { return desc->d; }
+
+ QJsonDocument makeDoc();
+ void loadDoc(const QJsonDocument &doc);
+
+ QAtomicInt ref;
+ QVector<QShaderDescription::InOutVariable> inVars;
+ QVector<QShaderDescription::InOutVariable> outVars;
+ QVector<QShaderDescription::UniformBlock> uniformBlocks;
+ QVector<QShaderDescription::PushConstantBlock> pushConstantBlocks;
+ QVector<QShaderDescription::StorageBlock> storageBlocks;
+ QVector<QShaderDescription::InOutVariable> combinedImageSamplers;
+ QVector<QShaderDescription::InOutVariable> storageImages;
+ std::array<uint, 3> localSize;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/rhi.pri b/src/gui/rhi/rhi.pri
new file mode 100644
index 0000000000..4297a5602b
--- /dev/null
+++ b/src/gui/rhi/rhi.pri
@@ -0,0 +1,57 @@
+HEADERS += \
+ rhi/qrhi_p.h \
+ rhi/qrhi_p_p.h \
+ rhi/qrhiprofiler_p.h \
+ rhi/qrhiprofiler_p_p.h \
+ rhi/qrhinull_p.h \
+ rhi/qrhinull_p_p.h \
+ rhi/qshader_p.h \
+ rhi/qshader_p_p.h \
+ rhi/qshaderdescription_p.h \
+ rhi/qshaderdescription_p_p.h
+
+SOURCES += \
+ rhi/qrhi.cpp \
+ rhi/qrhiprofiler.cpp \
+ rhi/qrhinull.cpp \
+ rhi/qshaderdescription.cpp \
+ rhi/qshader.cpp
+
+qtConfig(opengl) {
+ HEADERS += \
+ rhi/qrhigles2_p.h \
+ rhi/qrhigles2_p_p.h
+ SOURCES += \
+ rhi/qrhigles2.cpp
+}
+
+qtConfig(vulkan) {
+ HEADERS += \
+ rhi/qrhivulkan_p.h \
+ rhi/qrhivulkan_p_p.h
+ SOURCES += \
+ rhi/qrhivulkan.cpp
+}
+
+win32 {
+ HEADERS += \
+ rhi/qrhid3d11_p.h \
+ rhi/qrhid3d11_p_p.h
+ SOURCES += \
+ rhi/qrhid3d11.cpp
+
+ LIBS += -ld3d11 -ldxgi -ldxguid
+}
+
+# darwin {
+macos {
+ HEADERS += \
+ rhi/qrhimetal_p.h \
+ rhi/qrhimetal_p_p.h
+ SOURCES += \
+ rhi/qrhimetal.mm
+
+ LIBS += -framework AppKit -framework Metal
+}
+
+include($$PWD/../../3rdparty/VulkanMemoryAllocator.pri)
diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp
index dc7e128bcd..b5489c7ed9 100644
--- a/src/gui/text/qcssparser.cpp
+++ b/src/gui/text/qcssparser.cpp
@@ -67,6 +67,7 @@ struct QCssKnownValue
static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
+ { "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey },
{ "-qt-line-height-type", QtLineHeightType },
{ "-qt-list-indent", QtListIndent },
{ "-qt-list-number-prefix", QtListNumberPrefix },
diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h
index 62578f75e5..b0fa4be682 100644
--- a/src/gui/text/qcssparser_p.h
+++ b/src/gui/text/qcssparser_p.h
@@ -196,6 +196,7 @@ enum Property {
LineHeight,
QtLineHeightType,
FontKerning,
+ QtForegroundTextureCacheKey,
NumProperties
};
diff --git a/src/gui/text/qdistancefield.cpp b/src/gui/text/qdistancefield.cpp
index 5967c8d3de..17824a5ba1 100644
--- a/src/gui/text/qdistancefield.cpp
+++ b/src/gui/text/qdistancefield.cpp
@@ -782,7 +782,7 @@ bool qt_fontHasNarrowOutlines(QFontEngine *fontEngine)
if (glyph != 0)
im = fe->alphaMapForGlyph(glyph, QFixed(), QTransform());
- Q_ASSERT(fe->ref.load() == 0);
+ Q_ASSERT(fe->ref.loadRelaxed() == 0);
delete fe;
return imageHasNarrowOutlines(im);
@@ -899,11 +899,6 @@ QDistanceField::QDistanceField(int width, int height)
{
}
-QDistanceField::QDistanceField(const QDistanceField &other)
-{
- d = other.d;
-}
-
QDistanceField &QDistanceField::operator=(const QDistanceField &)
= default;
diff --git a/src/gui/text/qdistancefield_p.h b/src/gui/text/qdistancefield_p.h
index dc5d5a9a02..d6d8edd85d 100644
--- a/src/gui/text/qdistancefield_p.h
+++ b/src/gui/text/qdistancefield_p.h
@@ -94,7 +94,6 @@ public:
QDistanceField(const QRawFont &font, glyph_t glyph, bool doubleResolution = false);
QDistanceField(QFontEngine *fontEngine, glyph_t glyph, bool doubleResolution = false);
QDistanceField(const QPainterPath &path, glyph_t glyph, bool doubleResolution = false);
- QDistanceField(const QDistanceField &other);
QDistanceField &operator=(const QDistanceField &other);
bool isNull() const;
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index 164a2f60ab..97e73f0723 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -208,7 +208,7 @@ QFontPrivate::~QFontPrivate()
scFont = 0;
}
-extern QMutex *qt_fontdatabase_mutex();
+extern QRecursiveMutex *qt_fontdatabase_mutex();
#define QT_FONT_ENGINE_FROM_DATA(data, script) data->engines[script]
@@ -336,7 +336,7 @@ QFontEngineData::QFontEngineData()
QFontEngineData::~QFontEngineData()
{
- Q_ASSERT(ref.load() == 0);
+ Q_ASSERT(ref.loadRelaxed() == 0);
for (int i = 0; i < QChar::ScriptCount; ++i) {
if (engines[i]) {
if (!engines[i]->ref.deref())
@@ -604,7 +604,7 @@ QFont::QFont(QFontPrivate *data)
*/
void QFont::detach()
{
- if (d->ref.load() == 1) {
+ if (d->ref.loadRelaxed() == 1) {
if (d->engineData && !d->engineData->ref.deref())
delete d->engineData;
d->engineData = 0;
@@ -625,7 +625,7 @@ void QFont::detach()
*/
void QFontPrivate::detachButKeepEngineData(QFont *font)
{
- if (font->d->ref.load() == 1)
+ if (font->d->ref.loadRelaxed() == 1)
return;
QFontEngineData *engineData = font->d->engineData;
@@ -2799,12 +2799,12 @@ void QFontCache::cleanup()
cache->setLocalData(0);
}
-QBasicAtomicInt font_cache_id = Q_BASIC_ATOMIC_INITIALIZER(1);
+static QBasicAtomicInt font_cache_id = Q_BASIC_ATOMIC_INITIALIZER(0);
QFontCache::QFontCache()
: QObject(), total_cost(0), max_cost(min_cost),
current_timestamp(0), fast(false), timer_id(-1),
- m_id(font_cache_id.fetchAndAddRelaxed(1))
+ m_id(font_cache_id.fetchAndAddRelaxed(1) + 1)
{
}
@@ -2833,7 +2833,7 @@ void QFontCache::clear()
delete data;
} else {
FC_DEBUG("QFontCache::clear: engineData %p still has refcount %d",
- data, data->ref.load());
+ data, data->ref.loadRelaxed());
}
++it;
}
@@ -2857,7 +2857,7 @@ void QFontCache::clear()
delete engine;
} else if (cacheCount == 0) {
FC_DEBUG("QFontCache::clear: engine %p still has refcount %d",
- engine, engine->ref.load());
+ engine, engine->ref.loadRelaxed());
}
it.value().data = 0;
}
@@ -2927,7 +2927,7 @@ void QFontCache::updateHitCountAndTimeStamp(Engine &value)
FC_DEBUG("QFontCache: found font engine\n"
" %p: timestamp %4u hits %3u ref %2d/%2d, type %d",
value.data, value.timestamp, value.hits,
- value.data->ref.load(), engineCacheCount.value(value.data),
+ value.data->ref.loadRelaxed(), engineCacheCount.value(value.data),
value.data->type());
}
@@ -2937,7 +2937,7 @@ void QFontCache::insertEngine(const Key &key, QFontEngine *engine, bool insertMu
Q_ASSERT(key.multi == (engine->type() == QFontEngine::Multi));
#ifdef QFONTCACHE_DEBUG
- FC_DEBUG("QFontCache: inserting new engine %p, refcount %d", engine, engine->ref.load());
+ FC_DEBUG("QFontCache: inserting new engine %p, refcount %d", engine, engine->ref.loadRelaxed());
if (!insertMulti && engineCache.contains(key)) {
FC_DEBUG(" QFontCache already contains engine %p for key=(%g %g %d %d %d)",
engineCache.value(key).data, key.def.pointSize,
@@ -3026,9 +3026,9 @@ void QFontCache::decreaseCache()
EngineDataCache::ConstIterator it = engineDataCache.constBegin(),
end = engineDataCache.constEnd();
for (; it != end; ++it) {
- FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref.load()));
+ FC_DEBUG(" %p: ref %2d", it.value(), int(it.value()->ref.loadRelaxed()));
- if (it.value()->ref.load() != 1)
+ if (it.value()->ref.loadRelaxed() != 1)
in_use_cost += engine_data_cost;
}
}
@@ -3041,10 +3041,10 @@ void QFontCache::decreaseCache()
for (; it != end; ++it) {
FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, cost %u bytes",
it.value().data, it.value().timestamp, it.value().hits,
- it.value().data->ref.load(), engineCacheCount.value(it.value().data),
+ it.value().data->ref.loadRelaxed(), engineCacheCount.value(it.value().data),
it.value().data->cache_cost);
- if (it.value().data->ref.load() != 0)
+ if (it.value().data->ref.loadRelaxed() > engineCacheCount.value(it.value().data))
in_use_cost += it.value().data->cache_cost / engineCacheCount.value(it.value().data);
}
@@ -3093,7 +3093,7 @@ void QFontCache::decreaseCache()
// clean out all unused engine data
EngineDataCache::Iterator it = engineDataCache.begin();
while (it != engineDataCache.end()) {
- if (it.value()->ref.load() == 1) {
+ if (it.value()->ref.loadRelaxed() == 1) {
FC_DEBUG(" %p", it.value());
decreaseCost(sizeof(QFontEngineData));
it.value()->ref.deref();
@@ -3121,7 +3121,7 @@ void QFontCache::decreaseCache()
EngineCache::Iterator jt = end;
for ( ; it != end; ++it) {
- if (it.value().data->ref.load() != engineCacheCount.value(it.value().data))
+ if (it.value().data->ref.loadRelaxed() != engineCacheCount.value(it.value().data))
continue;
if (it.value().timestamp < oldest && it.value().hits <= least_popular) {
@@ -3135,7 +3135,7 @@ void QFontCache::decreaseCache()
if (it != end) {
FC_DEBUG(" %p: timestamp %4u hits %2u ref %2d/%2d, type %d",
it.value().data, it.value().timestamp, it.value().hits,
- it.value().data->ref.load(), engineCacheCount.value(it.value().data),
+ it.value().data->ref.loadRelaxed(), engineCacheCount.value(it.value().data),
it.value().data->type());
QFontEngine *fontEngine = it.value().data;
@@ -3150,7 +3150,7 @@ void QFontCache::decreaseCache()
}
}
// and delete the last occurrence
- Q_ASSERT(fontEngine->ref.load() == 0);
+ Q_ASSERT(fontEngine->ref.loadRelaxed() == 0);
decreaseCost(fontEngine->cache_cost);
delete fontEngine;
engineCacheCount.remove(fontEngine);
diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp
index 3cbda0facd..ce6bb0c347 100644
--- a/src/gui/text/qfontdatabase.cpp
+++ b/src/gui/text/qfontdatabase.cpp
@@ -451,8 +451,7 @@ class QFontDatabasePrivate
public:
QFontDatabasePrivate()
: count(0), families(0),
- fallbacksCache(64),
- reregisterAppFonts(false)
+ fallbacksCache(64)
{ }
~QFontDatabasePrivate() {
@@ -488,7 +487,6 @@ public:
};
QVector<ApplicationFont> applicationFonts;
int addAppFont(const QByteArray &fontData, const QString &fileName);
- bool reregisterAppFonts;
bool isApplicationFont(const QString &fileName);
void invalidate();
@@ -695,7 +693,8 @@ static QStringList familyList(const QFontDef &req)
if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"')))
|| (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\''))))
str = str.mid(1, str.length() - 2);
- family_list << str.toString();
+ if (!family_list.contains(str))
+ family_list << str.toString();
}
}
// append the substitute list for each family in family_list
@@ -706,7 +705,7 @@ static QStringList familyList(const QFontDef &req)
}
Q_GLOBAL_STATIC(QFontDatabasePrivate, privateDb)
-Q_GLOBAL_STATIC_WITH_ARGS(QMutex, fontDatabaseMutex, (QMutex::Recursive))
+Q_GLOBAL_STATIC(QRecursiveMutex, fontDatabaseMutex)
// used in qguiapplication.cpp
void qt_cleanupFontDatabase()
@@ -718,8 +717,8 @@ void qt_cleanupFontDatabase()
}
}
-// used in qfontengine_x11.cpp
-QMutex *qt_fontdatabase_mutex()
+// used in qfont.cpp
+QRecursiveMutex *qt_fontdatabase_mutex()
{
return fontDatabaseMutex();
}
@@ -896,15 +895,12 @@ static void initializeDb()
QFontDatabasePrivate *db = privateDb();
// init by asking for the platformfontdb for the first time or after invalidation
- if (!db->count)
+ if (!db->count) {
QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFontDatabase();
-
- if (db->reregisterAppFonts) {
for (int i = 0; i < db->applicationFonts.count(); i++) {
if (!db->applicationFonts.at(i).families.isEmpty())
registerFont(&db->applicationFonts[i]);
}
- db->reregisterAppFonts = false;
}
}
@@ -977,7 +973,7 @@ QFontEngine *loadSingleEngine(int script,
if (!engine->supportsScript(QChar::Script(script))) {
qWarning(" OpenType support missing for \"%s\", script %d",
+ qPrintable(def.family), script);
- if (engine->ref.load() == 0)
+ if (engine->ref.loadRelaxed() == 0)
delete engine;
return 0;
}
@@ -1032,11 +1028,7 @@ QFontEngine *loadEngine(int script, const QFontDef &request,
static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
{
- QFontDatabasePrivate *db = privateDb();
-
fnt->families = QGuiApplicationPrivate::platformIntegration()->fontDatabase()->addApplicationFont(fnt->data,fnt->fileName);
-
- db->reregisterAppFonts = true;
}
static QtFontStyle *bestStyle(QtFontFoundry *foundry, const QtFontStyle::Key &styleKey,
@@ -2450,13 +2442,18 @@ int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &
if (font.fileName.isEmpty() && !fontData.isEmpty())
font.fileName = QLatin1String(":qmemoryfonts/") + QString::number(i);
+ bool wasEmpty = privateDb()->count == 0;
registerFont(&font);
if (font.families.isEmpty())
return -1;
applicationFonts[i] = font;
- invalidate();
+ // If the cache has not yet been populated, we need to reload the application font later
+ if (wasEmpty)
+ invalidate();
+ else
+ emit qApp->fontDatabaseChanged();
return i;
}
@@ -2597,7 +2594,6 @@ bool QFontDatabase::removeApplicationFont(int handle)
db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont();
- db->reregisterAppFonts = true;
db->invalidate();
return true;
}
@@ -2826,7 +2822,7 @@ void QFontDatabase::load(const QFontPrivate *d, int script)
fe = QFontDatabase::findFont(req, script);
if (fe) {
if (fe->type() == QFontEngine::Box && !req.families.at(0).isEmpty()) {
- if (fe->ref.load() == 0)
+ if (fe->ref.loadRelaxed() == 0)
delete fe;
fe = 0;
} else {
diff --git a/src/gui/text/qglyphrun.cpp b/src/gui/text/qglyphrun.cpp
index bd44e11dce..3c16c3bf62 100644
--- a/src/gui/text/qglyphrun.cpp
+++ b/src/gui/text/qglyphrun.cpp
@@ -137,7 +137,7 @@ QGlyphRun::~QGlyphRun()
*/
void QGlyphRun::detach()
{
- if (d->ref.load() != 1)
+ if (d->ref.loadRelaxed() != 1)
d.detach();
}
diff --git a/src/gui/text/qplatformfontdatabase.cpp b/src/gui/text/qplatformfontdatabase.cpp
index 715b00d838..90322b24da 100644
--- a/src/gui/text/qplatformfontdatabase.cpp
+++ b/src/gui/text/qplatformfontdatabase.cpp
@@ -235,7 +235,7 @@ QSupportedWritingSystems::~QSupportedWritingSystems()
*/
void QSupportedWritingSystems::detach()
{
- if (d->ref.load() != 1) {
+ if (d->ref.loadRelaxed() != 1) {
QWritingSystemsPrivate *newd = new QWritingSystemsPrivate(d);
if (!d->ref.deref())
delete d;
diff --git a/src/gui/text/qrawfont_p.h b/src/gui/text/qrawfont_p.h
index dced165475..03259a94ed 100644
--- a/src/gui/text/qrawfont_p.h
+++ b/src/gui/text/qrawfont_p.h
@@ -87,7 +87,7 @@ public:
~QRawFontPrivate()
{
#ifndef QT_NO_DEBUG
- Q_ASSERT(ref.load() == 0);
+ Q_ASSERT(ref.loadRelaxed() == 0);
#endif
cleanUp();
}
diff --git a/src/gui/text/qstatictext.cpp b/src/gui/text/qstatictext.cpp
index dd894f4d32..490e0b6b8f 100644
--- a/src/gui/text/qstatictext.cpp
+++ b/src/gui/text/qstatictext.cpp
@@ -181,7 +181,7 @@ QStaticText::QStaticText(const QStaticText &other)
*/
QStaticText::~QStaticText()
{
- Q_ASSERT(!data || data->ref.load() >= 1);
+ Q_ASSERT(!data || data->ref.loadRelaxed() >= 1);
}
/*!
@@ -189,7 +189,7 @@ QStaticText::~QStaticText()
*/
void QStaticText::detach()
{
- if (data->ref.load() != 1)
+ if (data->ref.loadRelaxed() != 1)
data.detach();
}
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
index 899801ca39..dc34a96918 100644
--- a/src/gui/text/qtextdocument.cpp
+++ b/src/gui/text/qtextdocument.cpp
@@ -1364,6 +1364,8 @@ QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags op
blockOffset = 0;
}
} else {
+ if (blockOffset == block.length() - 1)
+ --blockOffset; // make sure to skip end-of-paragraph character
while (block.isValid()) {
if (findInBlock(block, subString, blockOffset, options, &cursor))
return cursor;
@@ -2304,7 +2306,11 @@ QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode)
if (mode == ExportEntireDocument) {
html += QLatin1String(" style=\"");
- emitFontFamily(defaultCharFormat.fontFamily());
+ QStringList fontFamilies = defaultCharFormat.fontFamilies().toStringList();
+ if (!fontFamilies.isEmpty())
+ emitFontFamily(fontFamilies);
+ else
+ emitFontFamily(defaultCharFormat.fontFamily());
if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) {
html += QLatin1String(" font-size:");
@@ -2366,8 +2372,12 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
bool attributesEmitted = false;
{
+ const QStringList families = format.fontFamilies().toStringList();
const QString family = format.fontFamily();
- if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
+ if (!families.isEmpty() && families != defaultCharFormat.fontFamilies().toStringList()) {
+ emitFontFamily(families);
+ attributesEmitted = true;
+ } else if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
emitFontFamily(family);
attributesEmitted = true;
}
@@ -2468,9 +2478,19 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
if (format.foreground() != defaultCharFormat.foreground()
&& format.foreground().style() != Qt::NoBrush) {
- html += QLatin1String(" color:");
- html += colorValue(format.foreground().color());
- html += QLatin1Char(';');
+ QBrush brush = format.foreground();
+ if (brush.style() == Qt::TexturePattern) {
+ const bool isPixmap = qHasPixmapTexture(brush);
+ const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
+
+ html += QLatin1String(" -qt-fg-texture-cachekey:");
+ html += QString::number(cacheKey);
+ html += QLatin1String(";");
+ } else {
+ html += QLatin1String(" color:");
+ html += colorValue(brush.color());
+ html += QLatin1Char(';');
+ }
attributesEmitted = true;
}
@@ -2644,6 +2664,27 @@ void QTextHtmlExporter::emitFontFamily(const QString &family)
html += QLatin1Char(';');
}
+void QTextHtmlExporter::emitFontFamily(const QStringList &families)
+{
+ html += QLatin1String(" font-family:");
+
+ bool first = true;
+ for (const QString &family : families) {
+ QLatin1String quote("\'");
+ if (family.contains(QLatin1Char('\'')))
+ quote = QLatin1String("&quot;");
+
+ if (!first)
+ html += QLatin1String(",");
+ else
+ first = false;
+ html += quote;
+ html += family.toHtmlEscaped();
+ html += quote;
+ }
+ html += QLatin1Char(';');
+}
+
void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right)
{
html += QLatin1String(" margin-top:");
diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h
index edb6bd9310..2fadc40cd2 100644
--- a/src/gui/text/qtextdocument.h
+++ b/src/gui/text/qtextdocument.h
@@ -225,6 +225,7 @@ public:
void print(QPagedPaintDevice *printer) const;
enum ResourceType {
+ UnknownResource = 0,
HtmlResource = 1,
ImageResource = 2,
StyleSheetResource = 3,
@@ -232,6 +233,7 @@ public:
UserResource = 100
};
+ Q_ENUM(ResourceType)
QVariant resource(int type, const QUrl &name) const;
void addResource(int type, const QUrl &name, const QVariant &resource);
diff --git a/src/gui/text/qtextdocument_p.h b/src/gui/text/qtextdocument_p.h
index a8e17bfc08..f4e7a25f22 100644
--- a/src/gui/text/qtextdocument_p.h
+++ b/src/gui/text/qtextdocument_p.h
@@ -357,6 +357,7 @@ public:
void mergeCachedResources(const QTextDocumentPrivate *priv);
+ friend struct QTextHtmlParserNode;
friend class QTextHtmlExporter;
friend class QTextCursor;
};
@@ -396,6 +397,7 @@ private:
void emitPageBreakPolicy(QTextFormat::PageBreakFlags policy);
void emitFontFamily(const QString &family);
+ void emitFontFamily(const QStringList &families);
void emitBackgroundAttribute(const QTextFormat &format);
QString findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap);
diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp
index e5cfa7f46e..a1b21b111b 100644
--- a/src/gui/text/qtextdocumentlayout.cpp
+++ b/src/gui/text/qtextdocumentlayout.cpp
@@ -973,8 +973,14 @@ void QTextDocumentLayoutPrivate::drawFrame(const QPointF &offset, QPainter *pain
if (pageHeight <= 0)
pageHeight = QFIXED_MAX;
- const int tableStartPage = (td->position.y / pageHeight).truncate();
- const int tableEndPage = ((td->position.y + td->size.height) / pageHeight).truncate();
+ QFixed absYPos = td->position.y;
+ QTextFrame *parentFrame = table->parentFrame();
+ while (parentFrame) {
+ absYPos += data(parentFrame)->position.y;
+ parentFrame = parentFrame->parentFrame();
+ }
+ const int tableStartPage = (absYPos / pageHeight).truncate();
+ const int tableEndPage = ((absYPos + td->size.height) / pageHeight).truncate();
qreal border = td->border.toReal();
drawFrameDecoration(painter, frame, fd, context.clip, frameRect);
@@ -1614,7 +1620,7 @@ QRectF QTextDocumentLayoutPrivate::layoutTable(QTextTable *table, int layoutFrom
for (int i = 0; i < children.count(); ++i) {
QTextFrame *frame = children.at(i);
QTextTableCell cell = table->cellAt(frame->firstPosition());
- td->childFrameMap.insertMulti(cell.row() + cell.column() * rows, frame);
+ td->childFrameMap.insert(cell.row() + cell.column() * rows, frame);
}
}
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
index 642f0893b4..1ee317d27c 100644
--- a/src/gui/text/qtexthtmlparser.cpp
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -1335,6 +1335,17 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
default: break;
}
break;
+
+ case QCss::QtForegroundTextureCacheKey:
+ {
+ if (resourceProvider != nullptr && resourceProvider->docHandle() != nullptr) {
+ bool ok;
+ qint64 searchKey = decl.d->values.first().variant.toLongLong(&ok);
+ if (ok)
+ applyForegroundImage(searchKey, resourceProvider);
+ }
+ break;
+ }
default: break;
}
}
@@ -1367,6 +1378,37 @@ void QTextHtmlParserNode::applyCssDeclarations(const QVector<QCss::Declaration>
#endif // QT_NO_CSSPARSER
+void QTextHtmlParserNode::applyForegroundImage(qint64 searchKey, const QTextDocument *resourceProvider)
+{
+ QTextDocumentPrivate *priv = resourceProvider->docHandle();
+ for (int i = 0; i < priv->formats.numFormats(); ++i) {
+ QTextCharFormat format = priv->formats.charFormat(i);
+ if (format.isValid()) {
+ QBrush brush = format.foreground();
+ if (brush.style() == Qt::TexturePattern) {
+ const bool isPixmap = qHasPixmapTexture(brush);
+
+ if (isPixmap && QCoreApplication::instance()->thread() != QThread::currentThread()) {
+ qWarning("Can't apply QPixmap outside of GUI thread");
+ return;
+ }
+
+ const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey();
+ if (cacheKey == searchKey) {
+ QBrush b;
+ if (isPixmap)
+ b.setTexture(brush.texture());
+ else
+ b.setTextureImage(brush.textureImage());
+ b.setStyle(Qt::TexturePattern);
+ charFormat.setForeground(b);
+ }
+ }
+ }
+ }
+
+}
+
void QTextHtmlParserNode::applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider)
{
if (!url.isEmpty() && resourceProvider) {
@@ -1506,7 +1548,16 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
n -= 3;
node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
} else if (key == QLatin1String("face")) {
- node->charFormat.setFontFamily(value);
+ if (value.contains(QLatin1Char(','))) {
+ const QStringList values = value.split(QLatin1Char(','));
+ QStringList families;
+ for (const QString &family : values)
+ families << family.trimmed();
+ node->charFormat.setFontFamilies(families);
+ node->charFormat.setFontFamily(families.at(0));
+ } else {
+ node->charFormat.setFontFamily(value);
+ }
} else if (key == QLatin1String("color")) {
QColor c; c.setNamedColor(value);
if (!c.isValid())
diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h
index 6ce294d211..c174b54a61 100644
--- a/src/gui/text/qtexthtmlparser_p.h
+++ b/src/gui/text/qtexthtmlparser_p.h
@@ -251,6 +251,7 @@ struct QTextHtmlParserNode {
void setListStyle(const QVector<QCss::Value> &cssValues);
#endif
+ void applyForegroundImage(qint64 cacheKey, const QTextDocument *resourceProvider);
void applyBackgroundImage(const QString &url, const QTextDocument *resourceProvider);
bool hasOnlyWhitespace() const;
diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp
index 8a9bb3953c..b96263f5fc 100644
--- a/src/gui/text/qtextmarkdownimporter.cpp
+++ b/src/gui/text/qtextmarkdownimporter.cpp
@@ -440,7 +440,7 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
#endif
case MD_TEXT_HTML:
// count how many tags are opened and how many are closed
-#if QT_CONFIG(regularexpression)
+#if QT_CONFIG(regularexpression) && QT_CONFIG(texthtmlparser)
{
int startIdx = 0;
while ((startIdx = s.indexOf(openingBracket, startIdx)) >= 0) {
diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp
index f351c8d20b..cbfb092485 100644
--- a/src/gui/text/qtextmarkdownwriter.cpp
+++ b/src/gui/text/qtextmarkdownwriter.cpp
@@ -47,6 +47,9 @@
#include "qtextcursor.h"
#include "qtextimagehandler_p.h"
#include "qloggingcategory.h"
+#if QT_CONFIG(itemmodel)
+#include "qabstractitemmodel.h"
+#endif
QT_BEGIN_NAMESPACE
@@ -70,6 +73,7 @@ bool QTextMarkdownWriter::writeAll(const QTextDocument *document)
return true;
}
+#if QT_CONFIG(itemmodel)
void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table)
{
QVector<int> tableColumnWidths(table->columnCount());
@@ -101,6 +105,7 @@ void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table)
}
m_listInfo.clear();
}
+#endif
void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
{
diff --git a/src/gui/text/qtextmarkdownwriter_p.h b/src/gui/text/qtextmarkdownwriter_p.h
index 90310250ac..c3076155d0 100644
--- a/src/gui/text/qtextmarkdownwriter_p.h
+++ b/src/gui/text/qtextmarkdownwriter_p.h
@@ -56,16 +56,19 @@
#include "qtextdocument_p.h"
#include "qtextdocumentwriter.h"
-#include "QAbstractTableModel"
QT_BEGIN_NAMESPACE
+class QAbstractItemModel;
+
class Q_GUI_EXPORT QTextMarkdownWriter
{
public:
QTextMarkdownWriter(QTextStream &stream, QTextDocument::MarkdownFeatures features);
bool writeAll(const QTextDocument *document);
+#if QT_CONFIG(itemmodel)
void writeTable(const QAbstractItemModel *table);
+#endif
int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat, bool ignoreEmpty);
void writeFrame(const QTextFrame *frame);
diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp
index 8c7cf8682c..ee0ff4c6ef 100644
--- a/src/gui/util/qdesktopservices.cpp
+++ b/src/gui/util/qdesktopservices.cpp
@@ -60,9 +60,9 @@ class QOpenUrlHandlerRegistry : public QObject
{
Q_OBJECT
public:
- inline QOpenUrlHandlerRegistry() : mutex(QMutex::Recursive) {}
+ QOpenUrlHandlerRegistry() = default;
- QMutex mutex;
+ QRecursiveMutex mutex;
struct Handler
{
diff --git a/src/gui/util/qshaderlanguage_p.h b/src/gui/util/qshaderlanguage_p.h
index 441f7a58d5..193f797cc3 100644
--- a/src/gui/util/qshaderlanguage_p.h
+++ b/src/gui/util/qshaderlanguage_p.h
@@ -59,8 +59,7 @@ QT_BEGIN_NAMESPACE
namespace QShaderLanguage
{
- Q_GUI_EXPORT
- Q_NAMESPACE
+ Q_NAMESPACE_EXPORT(Q_GUI_EXPORT)
enum StorageQualifier : char {
Const = 1,
diff --git a/src/gui/vulkan/vulkan.pri b/src/gui/vulkan/vulkan.pri
index 9bd7391235..5c902e8b82 100644
--- a/src/gui/vulkan/vulkan.pri
+++ b/src/gui/vulkan/vulkan.pri
@@ -17,39 +17,45 @@ qtConfig(vulkan) {
QMAKE_USE += vulkan/nolink
}
-# Generate qvulkanfunctions.h, qvulkanfunctions_p.h, qvulkanfunctions_p.cpp
-QMAKE_QVKGEN_INPUT = vulkan/vk.xml
-QMAKE_QVKGEN_LICENSE_HEADER = $$QT_SOURCE_TREE/header.LGPL
-qtPrepareTool(QMAKE_QVKGEN, qvkgen)
-
-qvkgen_h.commands = $$QMAKE_QVKGEN ${QMAKE_FILE_IN} $$shell_quote($$QMAKE_QVKGEN_LICENSE_HEADER) ${QMAKE_FILE_OUT_PATH}/${QMAKE_FILE_OUT_BASE}
-qvkgen_h.output = $$OUT_PWD/vulkan/qvulkanfunctions.h
-qvkgen_h.input = QMAKE_QVKGEN_INPUT
-qtConfig(vulkan): \
- qvkgen_h.variable_out = HEADERS
-else: \
- qvkgen_h.CONFIG += target_predeps no_link
-QMAKE_EXTRA_COMPILERS += qvkgen_h
-
-qvkgen_ph.commands = $$escape_expand(\\n)
-qvkgen_ph.output = $$OUT_PWD/vulkan/qvulkanfunctions_p.h
-qvkgen_ph.input = QMAKE_QVKGEN_INPUT
-qvkgen_ph.depends += $$OUT_PWD/vulkan/qvulkanfunctions.h
-qtConfig(vulkan): \
- qvkgen_ph.variable_out = HEADERS
-else: \
- qvkgen_ph.CONFIG += target_predeps no_link
-QMAKE_EXTRA_COMPILERS += qvkgen_ph
-
-qvkgen_pimpl.commands = $$escape_expand(\\n)
-qvkgen_pimpl.output = $$OUT_PWD/vulkan/qvulkanfunctions_p.cpp
-qvkgen_pimpl.input = QMAKE_QVKGEN_INPUT
-qvkgen_pimpl.depends += $$OUT_PWD/vulkan/qvulkanfunctions_p.h
-qtConfig(vulkan): \
- qvkgen_pimpl.variable_out = SOURCES
-else: \
- qvkgen_pimpl.CONFIG += target_predeps no_link
-QMAKE_EXTRA_COMPILERS += qvkgen_pimpl
+qtConfig(vkgen) {
+ # Generate qvulkanfunctions.h, qvulkanfunctions_p.h, qvulkanfunctions_p.cpp
+ QMAKE_QVKGEN_INPUT = vulkan/vk.xml
+ QMAKE_QVKGEN_LICENSE_HEADER = $$QT_SOURCE_TREE/header.LGPL
+ qtPrepareTool(QMAKE_QVKGEN, qvkgen)
+
+ qvkgen_h.commands = $$QMAKE_QVKGEN ${QMAKE_FILE_IN} $$shell_quote($$QMAKE_QVKGEN_LICENSE_HEADER) ${QMAKE_FILE_OUT_PATH}/${QMAKE_FILE_OUT_BASE}
+ qvkgen_h.output = $$OUT_PWD/vulkan/qvulkanfunctions.h
+ qvkgen_h.input = QMAKE_QVKGEN_INPUT
+ qtConfig(vulkan): \
+ qvkgen_h.variable_out = HEADERS
+ else: \
+ qvkgen_h.CONFIG += target_predeps no_link
+ QMAKE_EXTRA_COMPILERS += qvkgen_h
+
+ qvkgen_ph.commands = $$escape_expand(\\n)
+ qvkgen_ph.output = $$OUT_PWD/vulkan/qvulkanfunctions_p.h
+ qvkgen_ph.input = QMAKE_QVKGEN_INPUT
+ qvkgen_ph.depends += $$OUT_PWD/vulkan/qvulkanfunctions.h
+ qtConfig(vulkan): \
+ qvkgen_ph.variable_out = HEADERS
+ else: \
+ qvkgen_ph.CONFIG += target_predeps no_link
+ QMAKE_EXTRA_COMPILERS += qvkgen_ph
+
+ qvkgen_pimpl.commands = $$escape_expand(\\n)
+ qvkgen_pimpl.output = $$OUT_PWD/vulkan/qvulkanfunctions_p.cpp
+ qvkgen_pimpl.input = QMAKE_QVKGEN_INPUT
+ qvkgen_pimpl.depends += $$OUT_PWD/vulkan/qvulkanfunctions_p.h
+ qtConfig(vulkan): \
+ qvkgen_pimpl.variable_out = SOURCES
+ else: \
+ qvkgen_pimpl.CONFIG += target_predeps no_link
+ QMAKE_EXTRA_COMPILERS += qvkgen_pimpl
+} else {
+ # generate dummy files to make qmake happy
+ write_file($$OUT_PWD/vulkan/qvulkanfunctions.h)
+ write_file($$OUT_PWD/vulkan/qvulkanfunctions_p.h)
+}
# Ensure qvulkanfunctions.h gets installed correctly
targ_headers.CONFIG += no_check_exist