summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt150
-rw-r--r--src/gui/accessible/linux/atspiadaptor.cpp201
-rw-r--r--src/gui/accessible/linux/atspiadaptor_p.h6
-rw-r--r--src/gui/accessible/linux/dbusconnection.cpp12
-rw-r--r--src/gui/accessible/linux/dbusxml/Socket.xml2
-rw-r--r--src/gui/accessible/linux/qspi_constant_mappings.cpp5
-rw-r--r--src/gui/accessible/linux/qspiaccessiblebridge.cpp6
-rw-r--r--src/gui/accessible/linux/qspiapplicationadaptor.cpp14
-rw-r--r--src/gui/accessible/qaccessible.cpp170
-rw-r--r--src/gui/accessible/qaccessible.h45
-rw-r--r--src/gui/accessible/qaccessible_base.h15
-rw-r--r--src/gui/accessible/qaccessiblebridge.cpp2
-rw-r--r--src/gui/accessible/qaccessiblebridge.h4
-rw-r--r--src/gui/accessible/windows/apisupport/qwindowsuiawrapper.cpp89
-rw-r--r--src/gui/accessible/windows/apisupport/qwindowsuiawrapper_p.h67
-rw-r--r--src/gui/accessible/windows/apisupport/uiaattributeids_p.h63
-rw-r--r--src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h230
-rw-r--r--src/gui/accessible/windows/apisupport/uiacontroltypeids_p.h60
-rw-r--r--src/gui/accessible/windows/apisupport/uiaerrorids_p.h26
-rw-r--r--src/gui/accessible/windows/apisupport/uiaeventids_p.h54
-rw-r--r--src/gui/accessible/windows/apisupport/uiageneralids_p.h21
-rw-r--r--src/gui/accessible/windows/apisupport/uiapatternids_p.h53
-rw-r--r--src/gui/accessible/windows/apisupport/uiapropertyids_p.h188
-rw-r--r--src/gui/accessible/windows/apisupport/uiaserverinterfaces_p.h367
-rw-r--r--src/gui/accessible/windows/apisupport/uiatypes_p.h157
-rw-r--r--src/gui/compat/removed_api.cpp48
-rw-r--r--src/gui/configure.cmake49
-rw-r--r--src/gui/doc/images/qpainter-concentriccircles.pngbin31294 -> 95529 bytes
-rw-r--r--src/gui/doc/qtgui.qdocconf4
-rw-r--r--src/gui/doc/snippets/code/doc_src_richtext.qdoc2
-rw-r--r--src/gui/doc/snippets/code/src_gui_image_qicon.cpp28
-rw-r--r--src/gui/doc/snippets/code/src_gui_kernel_qguiapplication.cpp22
-rw-r--r--src/gui/doc/snippets/code/src_gui_painting_qpainter.cpp20
-rw-r--r--src/gui/doc/snippets/code/src_gui_vulkan_qvulkanfunctions.cpp10
-rw-r--r--src/gui/doc/snippets/rhioffscreen/main.cpp10
-rw-r--r--src/gui/doc/snippets/separations/separations.qdoc2
-rw-r--r--src/gui/doc/src/dnd.qdoc6
-rw-r--r--src/gui/doc/src/external-resources.qdoc11
-rw-r--r--src/gui/doc/src/qt6-changes.qdoc34
-rw-r--r--src/gui/doc/src/qtgui-overview.qdoc12
-rw-r--r--src/gui/doc/src/qtgui.qdoc17
-rw-r--r--src/gui/doc/src/richtext.qdoc9
-rw-r--r--src/gui/image/qabstractfileiconengine_p.h1
-rw-r--r--src/gui/image/qabstractfileiconprovider.cpp18
-rw-r--r--src/gui/image/qabstractfileiconprovider_p.h1
-rw-r--r--src/gui/image/qbmphandler.cpp2
-rw-r--r--src/gui/image/qicon.cpp693
-rw-r--r--src/gui/image/qicon.h161
-rw-r--r--src/gui/image/qicon_p.h22
-rw-r--r--src/gui/image/qiconengine.cpp75
-rw-r--r--src/gui/image/qiconengine.h1
-rw-r--r--src/gui/image/qiconengine_p.h59
-rw-r--r--src/gui/image/qiconloader.cpp284
-rw-r--r--src/gui/image/qiconloader_p.h47
-rw-r--r--src/gui/image/qimage.cpp804
-rw-r--r--src/gui/image/qimage.h12
-rw-r--r--src/gui/image/qimage_conversions.cpp60
-rw-r--r--src/gui/image/qimage_p.h180
-rw-r--r--src/gui/image/qimageiohandler.cpp6
-rw-r--r--src/gui/image/qimagereader.cpp102
-rw-r--r--src/gui/image/qimagereaderwriterhelpers.cpp10
-rw-r--r--src/gui/image/qimagereaderwriterhelpers_p.h2
-rw-r--r--src/gui/image/qmovie.cpp41
-rw-r--r--src/gui/image/qpixmap.cpp14
-rw-r--r--src/gui/image/qpixmap_win.cpp3
-rw-r--r--src/gui/image/qpixmapcache.cpp130
-rw-r--r--src/gui/image/qpixmapcache.h21
-rw-r--r--src/gui/image/qpixmapcache_p.h3
-rw-r--r--src/gui/image/qplatformpixmap.cpp18
-rw-r--r--src/gui/image/qpnghandler.cpp20
-rw-r--r--src/gui/image/qppmhandler.cpp15
-rw-r--r--src/gui/itemmodels/qfileinfogatherer.cpp126
-rw-r--r--src/gui/itemmodels/qfileinfogatherer_p.h13
-rw-r--r--src/gui/itemmodels/qfilesystemmodel.cpp121
-rw-r--r--src/gui/itemmodels/qfilesystemmodel.h13
-rw-r--r--src/gui/itemmodels/qfilesystemmodel_p.h19
-rw-r--r--src/gui/itemmodels/qstandarditemmodel.cpp75
-rw-r--r--src/gui/itemmodels/qstandarditemmodel_p.h2
-rw-r--r--src/gui/kernel/qaction.cpp6
-rw-r--r--src/gui/kernel/qaction_p.h2
-rw-r--r--src/gui/kernel/qactiongroup_p.h2
-rw-r--r--src/gui/kernel/qcursor.cpp30
-rw-r--r--src/gui/kernel/qdnd_p.h2
-rw-r--r--src/gui/kernel/qdrag.cpp4
-rw-r--r--src/gui/kernel/qevent.cpp49
-rw-r--r--src/gui/kernel/qevent.h14
-rw-r--r--src/gui/kernel/qeventpoint.cpp1
-rw-r--r--src/gui/kernel/qeventpoint.h1
-rw-r--r--src/gui/kernel/qeventpoint_p.h2
-rw-r--r--src/gui/kernel/qguiapplication.cpp179
-rw-r--r--src/gui/kernel/qguiapplication.h2
-rw-r--r--src/gui/kernel/qguiapplication_p.h13
-rw-r--r--src/gui/kernel/qguiapplication_platform.h34
-rw-r--r--src/gui/kernel/qguivariant.cpp5
-rw-r--r--src/gui/kernel/qhighdpiscaling_p.h4
-rw-r--r--src/gui/kernel/qinputdevice.cpp2
-rw-r--r--src/gui/kernel/qkeymapper.cpp74
-rw-r--r--src/gui/kernel/qkeymapper_p.h24
-rw-r--r--src/gui/kernel/qkeysequence.cpp151
-rw-r--r--src/gui/kernel/qkeysequence.h4
-rw-r--r--src/gui/kernel/qkeysequence_p.h8
-rw-r--r--src/gui/kernel/qopenglcontext.cpp24
-rw-r--r--src/gui/kernel/qpaintdevicewindow.cpp2
-rw-r--r--src/gui/kernel/qpaintdevicewindow_p.h4
-rw-r--r--src/gui/kernel/qpalette.cpp99
-rw-r--r--src/gui/kernel/qpalette.h7
-rw-r--r--src/gui/kernel/qpalette_p.h77
-rw-r--r--src/gui/kernel/qplatformdialoghelper.cpp31
-rw-r--r--src/gui/kernel/qplatformdialoghelper.h13
-rw-r--r--src/gui/kernel/qplatforminputcontext.cpp16
-rw-r--r--src/gui/kernel/qplatforminputcontext.h2
-rw-r--r--src/gui/kernel/qplatforminputcontextfactory.cpp29
-rw-r--r--src/gui/kernel/qplatforminputcontextfactory_p.h3
-rw-r--r--src/gui/kernel/qplatformintegration.cpp21
-rw-r--r--src/gui/kernel/qplatformintegration.h8
-rw-r--r--src/gui/kernel/qplatformkeymapper.cpp38
-rw-r--r--src/gui/kernel/qplatformkeymapper.h36
-rw-r--r--src/gui/kernel/qplatformscreen.h1
-rw-r--r--src/gui/kernel/qplatformscreen_p.h33
-rw-r--r--src/gui/kernel/qplatformservices.cpp2
-rw-r--r--src/gui/kernel/qplatformsystemtrayicon.h3
-rw-r--r--src/gui/kernel/qplatformtheme.cpp16
-rw-r--r--src/gui/kernel/qplatformtheme.h1
-rw-r--r--src/gui/kernel/qplatformwindow.cpp17
-rw-r--r--src/gui/kernel/qplatformwindow_p.h8
-rw-r--r--src/gui/kernel/qpointingdevice.cpp2
-rw-r--r--src/gui/kernel/qpointingdevice_p.h2
-rw-r--r--src/gui/kernel/qrasterwindow.cpp16
-rw-r--r--src/gui/kernel/qrasterwindow.h1
-rw-r--r--src/gui/kernel/qscreen.cpp21
-rw-r--r--src/gui/kernel/qscreen.h2
-rw-r--r--src/gui/kernel/qscreen_platform.h61
-rw-r--r--src/gui/kernel/qsessionmanager.cpp8
-rw-r--r--src/gui/kernel/qsessionmanager_p.h2
-rw-r--r--src/gui/kernel/qshortcut.cpp2
-rw-r--r--src/gui/kernel/qshortcut_p.h2
-rw-r--r--src/gui/kernel/qshortcutmap.cpp186
-rw-r--r--src/gui/kernel/qshortcutmap_p.h1
-rw-r--r--src/gui/kernel/qstylehints.cpp91
-rw-r--r--src/gui/kernel/qstylehints.h10
-rw-r--r--src/gui/kernel/qstylehints_p.h3
-rw-r--r--src/gui/kernel/qsurfaceformat.cpp2
-rw-r--r--src/gui/kernel/qtestsupport_gui.cpp23
-rw-r--r--src/gui/kernel/qtestsupport_gui.h1
-rw-r--r--src/gui/kernel/qwindow.cpp152
-rw-r--r--src/gui/kernel/qwindow_p.h19
-rw-r--r--src/gui/kernel/qwindowsysteminterface.cpp16
-rw-r--r--src/gui/kernel/qwindowsysteminterface.h5
-rw-r--r--src/gui/kernel/qwindowsysteminterface_p.h10
-rw-r--r--src/gui/math3d/qmatrix4x4.cpp2
-rw-r--r--src/gui/math3d/qquaternion.cpp88
-rw-r--r--src/gui/math3d/qquaternion.h197
-rw-r--r--src/gui/opengl/qopengl.h41
-rw-r--r--src/gui/opengl/qopenglextensions_p.h14
-rw-r--r--src/gui/opengl/qopenglfunctions.cpp26
-rw-r--r--src/gui/painting/qbackingstore.cpp35
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor.cpp140
-rw-r--r--src/gui/painting/qbackingstoredefaultcompositor_p.h33
-rw-r--r--src/gui/painting/qbackingstorerhisupport.cpp38
-rw-r--r--src/gui/painting/qbezier.cpp102
-rw-r--r--src/gui/painting/qbezier_p.h5
-rw-r--r--src/gui/painting/qblendfunctions.cpp4
-rw-r--r--src/gui/painting/qblendfunctions_p.h4
-rw-r--r--src/gui/painting/qcmyk_p.h88
-rw-r--r--src/gui/painting/qcolorclut_p.h127
-rw-r--r--src/gui/painting/qcolormatrix_p.h250
-rw-r--r--src/gui/painting/qcolorspace.cpp519
-rw-r--r--src/gui/painting/qcolorspace.h19
-rw-r--r--src/gui/painting/qcolorspace_p.h28
-rw-r--r--src/gui/painting/qcolortransferfunction_p.h76
-rw-r--r--src/gui/painting/qcolortransfertable_p.h106
-rw-r--r--src/gui/painting/qcolortransform.cpp1132
-rw-r--r--src/gui/painting/qcolortransform_p.h22
-rw-r--r--src/gui/painting/qcolortrc_p.h19
-rw-r--r--src/gui/painting/qcolortrclut.cpp77
-rw-r--r--src/gui/painting/qcolortrclut_p.h85
-rw-r--r--src/gui/painting/qcompositionfunctions.cpp30
-rw-r--r--src/gui/painting/qcoregraphics.mm35
-rw-r--r--src/gui/painting/qcoregraphics_p.h19
-rw-r--r--src/gui/painting/qcosmeticstroker.cpp4
-rw-r--r--src/gui/painting/qcssutil.cpp21
-rw-r--r--src/gui/painting/qdrawhelper.cpp182
-rw-r--r--src/gui/painting/qdrawhelper_avx2.cpp33
-rw-r--r--src/gui/painting/qdrawhelper_mips_dsp.cpp4
-rw-r--r--src/gui/painting/qdrawhelper_neon.cpp6
-rw-r--r--src/gui/painting/qdrawhelper_p.h9
-rw-r--r--src/gui/painting/qdrawhelper_sse2.cpp8
-rw-r--r--src/gui/painting/qicc.cpp979
-rw-r--r--src/gui/painting/qimageeffects.cpp327
-rw-r--r--src/gui/painting/qoutlinemapper.cpp2
-rw-r--r--src/gui/painting/qpagelayout.cpp196
-rw-r--r--src/gui/painting/qpagelayout.h13
-rw-r--r--src/gui/painting/qpageranges.cpp4
-rw-r--r--src/gui/painting/qpagesize.cpp4
-rw-r--r--src/gui/painting/qpagesize.h2
-rw-r--r--src/gui/painting/qpaintengine.cpp2
-rw-r--r--src/gui/painting/qpaintengine_raster.cpp42
-rw-r--r--src/gui/painting/qpaintengineex.cpp4
-rw-r--r--src/gui/painting/qpainter.cpp79
-rw-r--r--src/gui/painting/qpainterpath.cpp20
-rw-r--r--src/gui/painting/qpainterpath_p.h15
-rw-r--r--src/gui/painting/qpathclipper.cpp3
-rw-r--r--src/gui/painting/qpdf.cpp401
-rw-r--r--src/gui/painting/qpdf_p.h51
-rw-r--r--src/gui/painting/qpdfwriter.cpp47
-rw-r--r--src/gui/painting/qpdfwriter.h12
-rw-r--r--src/gui/painting/qpen.cpp132
-rw-r--r--src/gui/painting/qpen.h16
-rw-r--r--src/gui/painting/qpen_p.h5
-rw-r--r--src/gui/painting/qpixellayout.cpp129
-rw-r--r--src/gui/painting/qpixellayout_p.h8
-rw-r--r--src/gui/painting/qplatformbackingstore.h5
-rw-r--r--src/gui/painting/qpolygon.cpp34
-rw-r--r--src/gui/painting/qrasterbackingstore.cpp2
-rw-r--r--src/gui/painting/qregion.cpp66
-rw-r--r--src/gui/painting/qregion.h4
-rw-r--r--src/gui/painting/qrgbafloat.h26
-rw-r--r--src/gui/painting/qrgbafloat.qdoc14
-rw-r--r--src/gui/painting/qrhibackingstore.cpp30
-rw-r--r--src/gui/painting/qrhibackingstore_p.h1
-rw-r--r--src/gui/painting/qt_attribution.json12
-rw-r--r--src/gui/painting/qtransform.cpp181
-rw-r--r--src/gui/platform/android/qandroidnativeinterface.cpp16
-rw-r--r--src/gui/platform/darwin/qappleiconengine.mm464
-rw-r--r--src/gui/platform/darwin/qappleiconengine_p.h64
-rw-r--r--src/gui/platform/darwin/qapplekeymapper.mm159
-rw-r--r--src/gui/platform/darwin/qapplekeymapper_p.h14
-rw-r--r--src/gui/platform/darwin/qmacmimeregistry.mm14
-rw-r--r--src/gui/platform/darwin/qutimimeconverter.mm21
-rw-r--r--src/gui/platform/ios/PrivacyInfo.xcprivacy23
-rw-r--r--src/gui/platform/ios/qiosnativeinterface.cpp26
-rw-r--r--src/gui/platform/macos/qcocoanativeinterface.mm1
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp14
-rw-r--r--src/gui/platform/unix/dbustray/qdbustrayicon.cpp5
-rw-r--r--src/gui/platform/unix/qgenericunixservices.cpp22
-rw-r--r--src/gui/platform/unix/qgenericunixthemes.cpp277
-rw-r--r--src/gui/platform/unix/qunixnativeinterface.cpp26
-rw-r--r--src/gui/platform/unix/qxkbcommon.cpp79
-rw-r--r--src/gui/platform/unix/qxkbcommon_p.h53
-rw-r--r--src/gui/platform/wasm/qlocalfileapi.cpp16
-rw-r--r--src/gui/platform/wasm/qwasmlocalfileaccess.cpp68
-rw-r--r--src/gui/platform/windows/qwindowsguieventdispatcher.cpp2
-rw-r--r--src/gui/platform/windows/qwindowsmimeconverter.cpp18
-rw-r--r--src/gui/platform/windows/qwindowsnativeinterface.cpp26
-rw-r--r--src/gui/platform/windows/qwindowsthemecache.cpp79
-rw-r--r--src/gui/platform/windows/qwindowsthemecache_p.h35
-rw-r--r--src/gui/qt_cmdline.cmake3
-rw-r--r--src/gui/rhi/qrhi.cpp905
-rw-r--r--src/gui/rhi/qrhi.h72
-rw-r--r--src/gui/rhi/qrhi_p.h14
-rw-r--r--src/gui/rhi/qrhi_platform.h8
-rw-r--r--src/gui/rhi/qrhid3d11.cpp544
-rw-r--r--src/gui/rhi/qrhid3d11_p.h46
-rw-r--r--src/gui/rhi/qrhid3d12.cpp1114
-rw-r--r--src/gui/rhi/qrhid3d12_p.h122
-rw-r--r--src/gui/rhi/qrhid3dhelpers.cpp172
-rw-r--r--src/gui/rhi/qrhid3dhelpers_p.h53
-rw-r--r--src/gui/rhi/qrhigles2.cpp817
-rw-r--r--src/gui/rhi/qrhigles2_p.h81
-rw-r--r--src/gui/rhi/qrhimetal.mm413
-rw-r--r--src/gui/rhi/qrhimetal_p.h3
-rw-r--r--src/gui/rhi/qrhinull.cpp4
-rw-r--r--src/gui/rhi/qrhivulkan.cpp785
-rw-r--r--src/gui/rhi/qrhivulkan_p.h44
-rw-r--r--src/gui/rhi/qrhivulkanext_p.h48
-rw-r--r--src/gui/rhi/qshader.cpp73
-rw-r--r--src/gui/rhi/qshader.h4
-rw-r--r--src/gui/rhi/qshader_p.h3
-rw-r--r--src/gui/rhi/qshaderdescription.cpp12
-rw-r--r--src/gui/text/coretext/qcoretextfontdatabase.mm63
-rw-r--r--src/gui/text/coretext/qcoretextfontdatabase_p.h1
-rw-r--r--src/gui/text/coretext/qfontengine_coretext.mm49
-rw-r--r--src/gui/text/coretext/qfontengine_coretext_p.h4
-rw-r--r--src/gui/text/freetype/qfontengine_ft.cpp202
-rw-r--r--src/gui/text/freetype/qfontengine_ft_p.h7
-rw-r--r--src/gui/text/freetype/qfreetypefontdatabase.cpp141
-rw-r--r--src/gui/text/freetype/qfreetypefontdatabase_p.h9
-rw-r--r--src/gui/text/qabstracttextdocumentlayout.cpp2
-rw-r--r--src/gui/text/qabstracttextdocumentlayout_p.h2
-rw-r--r--src/gui/text/qcssparser.cpp96
-rw-r--r--src/gui/text/qcssparser_p.h24
-rw-r--r--src/gui/text/qdistancefield.cpp2
-rw-r--r--src/gui/text/qfont.cpp567
-rw-r--r--src/gui/text/qfont.h81
-rw-r--r--src/gui/text/qfont_p.h32
-rw-r--r--src/gui/text/qfontdatabase.cpp158
-rw-r--r--src/gui/text/qfontdatabase.h5
-rw-r--r--src/gui/text/qfontdatabase_p.h2
-rw-r--r--src/gui/text/qfontengine.cpp177
-rw-r--r--src/gui/text/qfontengine_p.h39
-rw-r--r--src/gui/text/qfontinfo.h2
-rw-r--r--src/gui/text/qfontmetrics.cpp2
-rw-r--r--src/gui/text/qfontmetrics.h2
-rw-r--r--src/gui/text/qfontsubset.cpp22
-rw-r--r--src/gui/text/qplatformfontdatabase.cpp12
-rw-r--r--src/gui/text/qplatformfontdatabase.h2
-rw-r--r--src/gui/text/qrawfont.cpp34
-rw-r--r--src/gui/text/qrawfont.h1
-rw-r--r--src/gui/text/qtextcursor.cpp4
-rw-r--r--src/gui/text/qtextdocument.cpp236
-rw-r--r--src/gui/text/qtextdocument.h14
-rw-r--r--src/gui/text/qtextdocument_p.cpp8
-rw-r--r--src/gui/text/qtextdocument_p.h5
-rw-r--r--src/gui/text/qtextdocumentfragment.cpp13
-rw-r--r--src/gui/text/qtextdocumentlayout.cpp34
-rw-r--r--src/gui/text/qtextengine.cpp119
-rw-r--r--src/gui/text/qtextengine_p.h22
-rw-r--r--src/gui/text/qtextformat.cpp76
-rw-r--r--src/gui/text/qtextformat.h8
-rw-r--r--src/gui/text/qtexthtmlparser.cpp155
-rw-r--r--src/gui/text/qtexthtmlparser_p.h2
-rw-r--r--src/gui/text/qtextimagehandler.cpp40
-rw-r--r--src/gui/text/qtextlayout.cpp405
-rw-r--r--src/gui/text/qtextlist.cpp14
-rw-r--r--src/gui/text/qtextmarkdownimporter.cpp137
-rw-r--r--src/gui/text/qtextmarkdownimporter_p.h10
-rw-r--r--src/gui/text/qtextmarkdownwriter.cpp165
-rw-r--r--src/gui/text/qtextmarkdownwriter_p.h3
-rw-r--r--src/gui/text/qtextobject.cpp2
-rw-r--r--src/gui/text/qtextoption.cpp2
-rw-r--r--src/gui/text/unix/qfontconfigdatabase.cpp229
-rw-r--r--src/gui/text/unix/qfontconfigdatabase_p.h1
-rw-r--r--src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp701
-rw-r--r--src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h24
-rw-r--r--src/gui/text/windows/qwindowsfontdatabase.cpp203
-rw-r--r--src/gui/text/windows/qwindowsfontdatabase_ft.cpp4
-rw-r--r--src/gui/text/windows/qwindowsfontdatabase_p.h15
-rw-r--r--src/gui/text/windows/qwindowsfontdatabasebase.cpp216
-rw-r--r--src/gui/text/windows/qwindowsfontdatabasebase_p.h14
-rw-r--r--src/gui/text/windows/qwindowsfontengine.cpp22
-rw-r--r--src/gui/text/windows/qwindowsfontengine_p.h4
-rw-r--r--src/gui/text/windows/qwindowsfontenginedirectwrite.cpp107
-rw-r--r--src/gui/text/windows/qwindowsfontenginedirectwrite_p.h15
-rw-r--r--src/gui/util/qastchandler.cpp6
-rw-r--r--src/gui/util/qdesktopservices.cpp42
-rw-r--r--src/gui/util/qgraphicsframecapture.cpp123
-rw-r--r--src/gui/util/qgraphicsframecapture_p.h55
-rw-r--r--src/gui/util/qgraphicsframecapture_p_p.h67
-rw-r--r--src/gui/util/qgraphicsframecapturemetal.mm169
-rw-r--r--src/gui/util/qgraphicsframecapturemetal_p_p.h57
-rw-r--r--src/gui/util/qgraphicsframecapturerenderdoc.cpp310
-rw-r--r--src/gui/util/qgraphicsframecapturerenderdoc_p_p.h53
-rw-r--r--src/gui/util/qgridlayoutengine.cpp19
-rw-r--r--src/gui/util/qgridlayoutengine_p.h3
-rw-r--r--src/gui/util/qktxhandler.cpp231
-rw-r--r--src/gui/util/qktxhandler_p.h4
-rw-r--r--src/gui/util/qvalidator.cpp93
-rw-r--r--src/gui/vulkan/licenseheader.h.in (renamed from src/gui/vulkan/generated_header.txt)0
-rw-r--r--src/gui/vulkan/qbasicvulkanplatforminstance.cpp9
-rw-r--r--src/gui/vulkan/qvulkanfunctions.cpp24
-rw-r--r--src/gui/vulkan/qvulkaninstance.cpp7
-rw-r--r--src/gui/vulkan/qvulkanwindow.cpp125
-rw-r--r--src/gui/vulkan/qvulkanwindow.h10
-rw-r--r--src/gui/vulkan/qvulkanwindow_p.h2
354 files changed, 19297 insertions, 7446 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 1b182d971d..aed66563a7 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -7,13 +7,15 @@ qt_find_package(WrapPNG PROVIDED_TARGETS WrapPNG::WrapPNG)
qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype)
if (QT_FEATURE_gui)
- if(WIN32)
+ if(QT_QPA_PLATFORMS)
+ list(GET QT_QPA_PLATFORMS 0 _default_platform)
+ elseif(WIN32)
set(_default_platform "windows")
elseif(ANDROID)
set(_default_platform "android")
elseif(MACOS)
set(_default_platform "cocoa")
- elseif(TVOS OR IOS)
+ elseif(UIKIT)
set(_default_platform "ios")
elseif(WATCHOS)
set(_default_platform "minimal")
@@ -30,6 +32,11 @@ if (QT_FEATURE_gui)
endif()
set(QT_QPA_DEFAULT_PLATFORM "${_default_platform}" CACHE STRING "QPA default platform")
+ if(NOT "${QT_QPA_DEFAULT_PLATFORM}" IN_LIST QT_QPA_PLATFORMS)
+ list(APPEND QT_QPA_PLATFORMS "${QT_QPA_DEFAULT_PLATFORM}")
+ set(QT_QPA_PLATFORMS "${QT_QPA_PLATFORMS}" CACHE STRING
+ "QPA platforms deployed by default" FORCE)
+ endif()
endif()
# Silence warnings in 3rdparty code
@@ -62,7 +69,7 @@ qt_internal_add_module(Gui
image/qbitmap.cpp image/qbitmap.h
image/qbmphandler.cpp image/qbmphandler_p.h
image/qicon.cpp image/qicon.h image/qicon_p.h
- image/qiconengine.cpp image/qiconengine.h
+ image/qiconengine.cpp image/qiconengine.h image/qiconengine_p.h
image/qiconengineplugin.cpp image/qiconengineplugin.h
image/qiconloader.cpp image/qiconloader_p.h
image/qimage.cpp image/qimage.h image/qimage_p.h
@@ -101,7 +108,7 @@ qt_internal_add_module(Gui
kernel/qoffscreensurface_platform.h
kernel/qopenglcontext.h
kernel/qpaintdevicewindow.cpp kernel/qpaintdevicewindow.h kernel/qpaintdevicewindow_p.h
- kernel/qpalette.cpp kernel/qpalette.h
+ kernel/qpalette.cpp kernel/qpalette.h kernel/qpalette_p.h
kernel/qpixelformat.cpp kernel/qpixelformat.h
kernel/qplatformclipboard.cpp kernel/qplatformclipboard.h
kernel/qplatformcursor.cpp kernel/qplatformcursor.h
@@ -114,6 +121,7 @@ qt_internal_add_module(Gui
kernel/qplatformintegration.cpp kernel/qplatformintegration.h
kernel/qplatformintegrationfactory.cpp kernel/qplatformintegrationfactory_p.h
kernel/qplatformintegrationplugin.cpp kernel/qplatformintegrationplugin.h
+ kernel/qplatformkeymapper.cpp kernel/qplatformkeymapper.h
kernel/qplatformmenu.cpp kernel/qplatformmenu.h kernel/qplatformmenu_p.h
kernel/qplatformnativeinterface.cpp kernel/qplatformnativeinterface.h
kernel/qplatformoffscreensurface.cpp kernel/qplatformoffscreensurface.h
@@ -130,7 +138,7 @@ qt_internal_add_module(Gui
kernel/qplatformwindow.cpp kernel/qplatformwindow.h kernel/qplatformwindow_p.h
kernel/qpointingdevice.cpp kernel/qpointingdevice.h kernel/qpointingdevice_p.h
kernel/qrasterwindow.cpp kernel/qrasterwindow.h
- kernel/qscreen.cpp kernel/qscreen.h kernel/qscreen_p.h
+ kernel/qscreen.cpp kernel/qscreen.h kernel/qscreen_p.h kernel/qscreen_platform.h
kernel/qsessionmanager.cpp kernel/qsessionmanager.h kernel/qsessionmanager_p.h
kernel/qstylehints.cpp kernel/qstylehints.h kernel/qstylehints_p.h
kernel/qsurface.cpp kernel/qsurface.h
@@ -159,6 +167,7 @@ qt_internal_add_module(Gui
painting/qblittable.cpp painting/qblittable_p.h
painting/qbrush.cpp painting/qbrush.h
painting/qcolor.cpp painting/qcolor.h painting/qcolor_p.h
+ painting/qcolorclut_p.h
painting/qcolormatrix_p.h
painting/qcolorspace.cpp painting/qcolorspace.h painting/qcolorspace_p.h
painting/qcolortransferfunction_p.h
@@ -168,6 +177,7 @@ qt_internal_add_module(Gui
painting/qcolortrclut.cpp painting/qcolortrclut_p.h
painting/qcompositionfunctions.cpp
painting/qcosmeticstroker.cpp painting/qcosmeticstroker_p.h
+ painting/qcmyk_p.h
painting/qdatabuffer_p.h
painting/qdrawhelper_p.h
painting/qdrawhelper_x86_p.h
@@ -176,6 +186,7 @@ qt_internal_add_module(Gui
painting/qfixed_p.h
painting/qgrayraster.c painting/qgrayraster_p.h
painting/qicc.cpp painting/qicc_p.h
+ painting/qimageeffects.cpp
painting/qimagescale.cpp painting/qimagescale_p.h
painting/qmath_p.h
painting/qmemrotate.cpp painting/qmemrotate_p.h
@@ -259,8 +270,10 @@ qt_internal_add_module(Gui
util/qtexturefilereader.cpp util/qtexturefilereader_p.h
util/qvalidator.cpp util/qvalidator.h
DEFINES
+ QT_NO_CONTEXTLESS_CONNECT
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
+ QT_USE_NODISCARD_FILE_OPEN
QT_QPA_DEFAULT_PLATFORM_NAME="${QT_QPA_DEFAULT_PLATFORM}"
INCLUDE_DIRECTORIES
../3rdparty/VulkanMemoryAllocator
@@ -284,27 +297,28 @@ qt_internal_add_module(Gui
)
# Resources:
-set_source_files_properties("../3rdparty/icc/sRGB2014.icc"
- PROPERTIES QT_RESOURCE_ALIAS "sRGB2014.icc"
-)
-set(qpdf_resource_files
- "../3rdparty/icc/sRGB2014.icc"
- "painting/qpdfa_metadata.xml"
-)
+if(QT_FEATURE_pdf)
+ set_source_files_properties("../3rdparty/icc/sRGB2014.icc"
+ PROPERTIES QT_RESOURCE_ALIAS "sRGB2014.icc"
+ )
+ set(qpdf_resource_files
+ "../3rdparty/icc/sRGB2014.icc"
+ "painting/qpdfa_metadata.xml"
+ )
+ qt_internal_add_resource(Gui "qpdf"
+ PREFIX
+ "/qpdf/"
+ BASE
+ "painting"
+ FILES
+ ${qpdf_resource_files}
+ )
+endif()
if(WIN32 OR (UNIX AND NOT APPLE))
set_target_properties(Gui PROPERTIES UNITY_BUILD OFF) # X11 define clashes/Windows oddities.
endif()
-qt_internal_add_resource(Gui "qpdf"
- PREFIX
- "/qpdf/"
- BASE
- "painting"
- FILES
- ${qpdf_resource_files}
-)
-
qt_internal_add_resource(Gui "gui_shaders"
PREFIX
"/qt-project.org/gui"
@@ -336,9 +350,10 @@ if(QT_FEATURE_opengl)
target_link_libraries(Gui PUBLIC GLESv2::GLESv2)
if(INTEGRITY AND _qt_igy_gui_libs)
- find_package(IntegrityPlatformGraphics)
- target_link_libraries(Gui
- INTERFACE $<LINK_ONLY:IntegrityPlatformGraphics::IntegrityPlatformGraphics>)
+ qt_internal_extend_target(Gui
+ LIBRARIES
+ IntegrityPlatformGraphics::IntegrityPlatformGraphics
+ )
endif()
elseif(NOT QT_FEATURE_opengl_dynamic)
@@ -368,6 +383,11 @@ qt_internal_extend_target(Gui CONDITION MACOS
${FWAppKit}
)
+qt_internal_extend_target(Gui CONDITION UIKIT
+ SOURCES
+ platform/ios/qiosnativeinterface.cpp
+)
+
qt_internal_extend_target(Gui CONDITION WASM
SOURCES
platform/wasm/qwasmnativeinterface.cpp
@@ -382,6 +402,7 @@ qt_internal_extend_target(Gui CONDITION APPLE
platform/darwin/qmacmimeregistry.mm platform/darwin/qmacmimeregistry_p.h
platform/darwin/qutimimeconverter.mm platform/darwin/qutimimeconverter.h
platform/darwin/qapplekeymapper.mm platform/darwin/qapplekeymapper_p.h
+ platform/darwin/qappleiconengine.mm platform/darwin/qappleiconengine_p.h
text/coretext/qcoretextfontdatabase.mm text/coretext/qcoretextfontdatabase_p.h
text/coretext/qfontengine_coretext.mm text/coretext/qfontengine_coretext_p.h
LIBRARIES
@@ -393,6 +414,12 @@ qt_internal_extend_target(Gui CONDITION APPLE
${FWImageIO}
)
+qt_internal_extend_target(Gui CONDITION QNX
+ SOURCES
+ painting/qrasterbackingstore.cpp painting/qrasterbackingstore_p.h
+ painting/qrhibackingstore.cpp painting/qrhibackingstore_p.h
+)
+
qt_internal_extend_target(Gui CONDITION QT_FEATURE_animation
SOURCES
animation/qguivariantanimation.cpp
@@ -405,7 +432,9 @@ qt_internal_extend_target(Gui CONDITION WIN32
platform/windows/qwindowsguieventdispatcher.cpp platform/windows/qwindowsguieventdispatcher_p.h
platform/windows/qwindowsmimeconverter.h platform/windows/qwindowsmimeconverter.cpp
platform/windows/qwindowsnativeinterface.cpp
+ platform/windows/qwindowsthemecache.cpp platform/windows/qwindowsthemecache_p.h
rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h
+ rhi/qrhid3dhelpers.cpp rhi/qrhid3dhelpers_p.h
rhi/vs_test_p.h
rhi/qrhid3d12.cpp rhi/qrhid3d12_p.h
rhi/cs_mipmap_p.h
@@ -421,14 +450,36 @@ qt_internal_extend_target(Gui CONDITION WIN32
ole32
shell32
user32
+ uxtheme
PUBLIC_LIBRARIES
d3d11
dxgi
dxguid
- dcomp
d3d12
)
+if(QT_FEATURE_graphicsframecapture)
+ qt_internal_extend_target(Gui
+ SOURCES
+ util/qgraphicsframecapture_p.h util/qgraphicsframecapture.cpp
+ util/qgraphicsframecapture_p_p.h
+ )
+
+ qt_internal_extend_target(Gui CONDITION (WIN32 OR (UNIX AND NOT APPLE)) AND QT_FEATURE_library
+ LIBRARIES
+ RenderDoc::RenderDoc
+ SOURCES
+ util/qgraphicsframecapturerenderdoc_p_p.h util/qgraphicsframecapturerenderdoc.cpp
+ )
+
+ qt_internal_extend_target(Gui CONDITION IOS OR MACOS
+ SOURCES
+ util/qgraphicsframecapturemetal_p_p.h util/qgraphicsframecapturemetal.mm
+ PUBLIC_LIBRARIES
+ ${FWMetal}
+ )
+endif()
+
if(QT_FEATURE_egl)
qt_find_package(EGL)
endif()
@@ -442,9 +493,15 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_egl
EGL::EGL
)
-qt_internal_extend_target(Gui CONDITION QT_FEATURE_accessibility
- CONDITION_INDEPENDENT_SOURCES
+# These two headers are always installed, their contents are guarded with
+# "#if QT_CONFIG(accessibility)", so if QT_FEATURE_accessibility is not
+# enabled, they are just duds.
+qt_internal_extend_target(Gui
+ SOURCES
accessible/qaccessible.h accessible/qplatformaccessibility.h
+)
+
+qt_internal_extend_target(Gui CONDITION QT_FEATURE_accessibility
SOURCES
accessible/qaccessible.cpp accessible/qaccessible_base.h
accessible/qaccessiblebridge.cpp accessible/qaccessiblebridge.h
@@ -462,21 +519,6 @@ qt_internal_extend_target(Gui CONDITION APPLE AND QT_FEATURE_accessibility
${FWFoundation}
)
-qt_internal_extend_target(Gui CONDITION QT_FEATURE_accessibility AND WIN32
- SOURCES
- accessible/windows/apisupport/qwindowsuiawrapper.cpp accessible/windows/apisupport/qwindowsuiawrapper_p.h
- accessible/windows/apisupport/uiaattributeids_p.h
- accessible/windows/apisupport/uiaclientinterfaces_p.h
- accessible/windows/apisupport/uiacontroltypeids_p.h
- accessible/windows/apisupport/uiaerrorids_p.h
- accessible/windows/apisupport/uiaeventids_p.h
- accessible/windows/apisupport/uiageneralids_p.h
- accessible/windows/apisupport/uiapatternids_p.h
- accessible/windows/apisupport/uiapropertyids_p.h
- accessible/windows/apisupport/uiaserverinterfaces_p.h
- accessible/windows/apisupport/uiatypes_p.h
-)
-
if(QT_FEATURE_accessibility AND QT_FEATURE_accessibility_atspi_bridge)
set(atspi_accessibility ON)
else()
@@ -542,7 +584,10 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_png
WrapPNG::WrapPNG
)
-qt_internal_extend_target(Gui CONDITION ((QT_FEATURE_png) AND (WIN32 AND MINGW)) AND (GCC_VERSION___equals___8.1.0)
+qt_internal_extend_target(Gui
+ CONDITION
+ QT_FEATURE_png AND WIN32 AND MINGW AND
+ (CMAKE_CXX_COMPILER_VESION VERSION_EQUAL "8.1.0")
COMPILE_OPTIONS
-fno-reorder-blocks-and-partition
)
@@ -604,6 +649,8 @@ endif()
qt_internal_extend_target(Gui CONDITION ANDROID
SOURCES
platform/android/qandroidnativeinterface.cpp
+ painting/qrasterbackingstore.cpp painting/qrasterbackingstore_p.h
+ painting/qrhibackingstore.cpp painting/qrhibackingstore_p.h
)
qt_internal_extend_target(Gui CONDITION ANDROID AND (TEST_architecture_arch STREQUAL arm64 OR TEST_architecture_arch STREQUAL arm)
@@ -827,7 +874,7 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_filesystemmodel
qt_internal_extend_target(Gui CONDITION QT_FEATURE_vulkan
SOURCES
- rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h rhi/qrhivulkanext_p.h
+ rhi/qrhivulkan.cpp rhi/qrhivulkan_p.h
vulkan/qbasicvulkanplatforminstance.cpp vulkan/qbasicvulkanplatforminstance_p.h
vulkan/qplatformvulkaninstance.cpp vulkan/qplatformvulkaninstance.h
vulkan/qvulkandefaultinstance.cpp vulkan/qvulkandefaultinstance_p.h
@@ -866,7 +913,7 @@ if (QT_FEATURE_vulkan)
list(APPEND vulkan_fun_command_content
COMMAND "${qvkgen}"
"${CMAKE_CURRENT_SOURCE_DIR}/vulkan/vk.xml"
- "${CMAKE_CURRENT_SOURCE_DIR}/vulkan/generated_header.txt"
+ "${CMAKE_CURRENT_SOURCE_DIR}/vulkan/licenseheader.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/vulkan/qvulkanfunctions"
DEPENDS vulkan/vk.xml ${qvkgen}
COMMENT "Generating vulkan data"
@@ -899,10 +946,14 @@ qt_internal_extend_target(Gui CONDITION WASM
qt_internal_extend_target(Gui CONDITION UNIX
SOURCES
+ platform/unix/qunixnativeinterface.cpp
+)
+
+qt_internal_extend_target(Gui CONDITION UNIX AND NOT WASM
+ SOURCES
platform/unix/qgenericunixeventdispatcher.cpp platform/unix/qgenericunixeventdispatcher_p.h
platform/unix/qunixeventdispatcher.cpp
platform/unix/qunixeventdispatcher_qpa_p.h
- platform/unix/qunixnativeinterface.cpp
)
qt_internal_extend_target(Gui CONDITION QT_FEATURE_glib AND UNIX
@@ -960,7 +1011,7 @@ qt_internal_extend_target(Gui CONDITION QT_FEATURE_xkbcommon AND UNIX
XKB::XKB
)
-qt_internal_extend_target(Gui CONDITION IOS OR MACOS
+qt_internal_extend_target(Gui CONDITION QT_FEATURE_metal
SOURCES
rhi/qrhimetal.mm rhi/qrhimetal_p.h
PUBLIC_LIBRARIES
@@ -989,4 +1040,9 @@ qt_internal_add_docs(Gui
doc/qtgui.qdocconf
)
+if(IOS)
+ qt_internal_set_apple_privacy_manifest(Gui
+ "${CMAKE_CURRENT_SOURCE_DIR}/platform/ios/PrivacyInfo.xcprivacy")
+endif()
+
qt_internal_add_optimize_full_flags()
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp
index e6ada2c240..a83dec4e64 100644
--- a/src/gui/accessible/linux/atspiadaptor.cpp
+++ b/src/gui/accessible/linux/atspiadaptor.cpp
@@ -16,6 +16,7 @@
#if QT_CONFIG(accessibility)
#include "socket_interface.h"
#include "qspi_constant_mappings_p.h"
+#include <QtCore/private/qstringiterator_p.h>
#include <QtGui/private/qaccessiblebridgeutils_p.h>
#include "qspiapplicationadaptor_p.h"
@@ -34,6 +35,13 @@
#define ATSPI_COORD_TYPE_PARENT 2
#endif
+// ATSPI_*_VERSION defines were added in libatspi 2.50,
+// as was the AtspiLive enum; define values here for older versions
+#if !defined(ATSPI_MAJOR_VERSION) || !defined(ATSPI_MINOR_VERSION) || ATSPI_MAJOR_VERSION < 2 || ATSPI_MINOR_VERSION < 50
+#define ATSPI_LIVE_POLITE 1
+#define ATSPI_LIVE_ASSERTIVE 2
+#endif
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -46,6 +54,7 @@ AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
, sendFocus(0)
, sendObject(0)
, sendObject_active_descendant_changed(0)
+ , sendObject_announcement(0)
, sendObject_attributes_changed(0)
, sendObject_bounds_changed(0)
, sendObject_children_changed(0)
@@ -126,6 +135,7 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <interface name=\"org.a11y.atspi.Accessible\">\n"
" <property access=\"read\" type=\"s\" name=\"Name\"/>\n"
" <property access=\"read\" type=\"s\" name=\"Description\"/>\n"
+ " <property access=\"read\" type=\"s\" name=\"HelpText\"/>\n"
" <property access=\"read\" type=\"(so)\" name=\"Parent\">\n"
" <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n"
" </property>\n"
@@ -468,6 +478,14 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" name=\"row_extents\" type=\"i\" />\n"
" <arg direction=\"out\" name=\"col_extents\" type=\"i\" />\n"
" </method>\n"
+ " <method name=\"GetColumnHeaderCells\">\n"
+ " <arg direction=\"out\" type=\"a(so)\"/>\n"
+ " <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n"
+ " </method>\n"
+ " <method name=\"GetRowHeaderCells\">\n"
+ " <arg direction=\"out\" type=\"a(so)\"/>\n"
+ " <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n"
+ " </method>\n"
" </interface>\n"
);
@@ -669,6 +687,8 @@ void AtSpiAdaptor::setBitFlag(const QString &flag)
if (false) {
} else if (right.startsWith("ActiveDescendantChanged"_L1)) {
sendObject_active_descendant_changed = 1;
+ } else if (right.startsWith("Announcement"_L1)) {
+ sendObject_announcement = 1;
} else if (right.startsWith("AttributesChanged"_L1)) {
sendObject_attributes_changed = 1;
} else if (right.startsWith("BoundsChanged"_L1)) {
@@ -920,6 +940,26 @@ void AtSpiAdaptor::notifyStateChange(QAccessibleInterface *interface, const QStr
sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "StateChanged"_L1, stateArgs);
}
+void AtSpiAdaptor::sendAnnouncement(QAccessibleAnnouncementEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ if (!iface) {
+ qCWarning(lcAccessibilityAtspi, "Announcement event has no accessible set.");
+ return;
+ }
+ if (!iface->isValid()) {
+ qCWarning(lcAccessibilityAtspi) << "Announcement event with invalid accessible: " << iface;
+ return;
+ }
+
+ const QString path = pathForInterface(iface);
+ const QString message = event->message();
+ const QAccessible::AnnouncementPriority prio = event->priority();
+ const int politeness = (prio == QAccessible::AnnouncementPriority::Assertive) ? ATSPI_LIVE_ASSERTIVE : ATSPI_LIVE_POLITE;
+
+ const QVariantList args = packDBusSignalArguments(QString(), politeness, 0, QVariant::fromValue(QDBusVariant(message)));
+ sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "Announcement"_L1, args);
+}
/*!
This function gets called when Qt notifies about accessibility updates.
@@ -994,6 +1034,14 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event)
sendFocusChanged(event->accessibleInterface());
break;
}
+
+ case QAccessible::Announcement: {
+ if (sendObject || sendObject_announcement) {
+ QAccessibleAnnouncementEvent *announcementEvent = static_cast<QAccessibleAnnouncementEvent*>(event);
+ sendAnnouncement(announcementEvent);
+ }
+ break;
+ }
case QAccessible::TextInserted:
case QAccessible::TextRemoved:
case QAccessible::TextUpdated: {
@@ -1577,6 +1625,8 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS
sendReply(connection, message, accessibleInterfaces(interface));
} else if (function == "GetDescription"_L1) {
sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Description))));
+ } else if (function == "GetHelpText"_L1) {
+ sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Help))));
} else if (function == "GetState"_L1) {
quint64 spiState = spiStatesFromQState(interface->state());
if (interface->tableInterface()) {
@@ -1597,7 +1647,7 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS
sendReply(connection, message,
QVariant::fromValue(spiStateSetFromSpiStates(spiState)));
} else if (function == "GetAttributes"_L1) {
- sendReply(connection, message, QVariant::fromValue(QSpiAttributeSet()));
+ sendReply(connection, message, QVariant::fromValue(getAttributes(interface)));
} else if (function == "GetRelationSet"_L1) {
sendReply(connection, message, QVariant::fromValue(relationSet(interface, connection)));
} else if (function == "GetApplication"_L1) {
@@ -1741,15 +1791,14 @@ bool AtSpiAdaptor::inheritsQAction(QObject *object)
// Component
static QAccessibleInterface * getWindow(QAccessibleInterface * interface)
{
- if (interface->role() == QAccessible::Dialog || interface->role() == QAccessible::Window)
- return interface;
-
- QAccessibleInterface * parent = interface->parent();
- while (parent && parent->role() != QAccessible::Dialog
- && parent->role() != QAccessible::Window)
- parent = parent->parent();
-
- return parent;
+ // find top-level window in a11y hierarchy (either has a
+ // corresponding role or is a direct child of the application object)
+ QAccessibleInterface* app = QAccessible::queryAccessibleInterface(qApp);
+ while (interface && interface->role() != QAccessible::Dialog
+ && interface->role() != QAccessible::Window && interface->parent() != app)
+ interface = interface->parent();
+
+ return interface;
}
bool AtSpiAdaptor::componentInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection)
@@ -1980,8 +2029,13 @@ bool AtSpiAdaptor::textInterface(QAccessibleInterface *interface, const QString
int offset = message.arguments().at(0).toInt();
int start;
int end;
- QString result = interface->textInterface()->textAtOffset(offset, QAccessible::CharBoundary, &start, &end);
- sendReply(connection, message, (int) *(qPrintable (result)));
+ const QString charString = interface->textInterface()
+ ->textAtOffset(offset, QAccessible::CharBoundary, &start, &end);
+ int codePoint = 0;
+ QStringIterator stringIt(charString);
+ if (stringIt.hasNext())
+ codePoint = static_cast<int>(stringIt.peekNext());
+ sendReply(connection, message, codePoint);
} else if (function == "GetCharacterExtents"_L1) {
int offset = message.arguments().at(0).toInt();
int coordType = message.arguments().at(1).toUInt();
@@ -2147,7 +2201,7 @@ namespace
QString atspiColor(const QString &ia2Color)
{
// "rgb(%u,%u,%u)" -> "%u,%u,%u"
- return ia2Color.mid(4, ia2Color.size() - (4+1));
+ return ia2Color.mid(4, ia2Color.size() - (4+1)).replace(u"\\,"_s, u","_s);
}
QString atspiSize(const QString &ia2Size)
@@ -2161,9 +2215,9 @@ namespace
QString name = ia2Name;
QString value = ia2Value;
- // IAccessible2: http://www.linuxfoundation.org/collaborate/workgroups/accessibility/iaccessible2/textattributes
- // ATK attribute names: https://git.gnome.org/browse/orca/tree/src/orca/text_attribute_names.py
- // ATK attribute values: https://developer.gnome.org/atk/unstable/AtkText.html#AtkTextAttribute
+ // IAccessible2: https://wiki.linuxfoundation.org/accessibility/iaccessible2/textattributes
+ // ATK attribute names: https://gitlab.gnome.org/GNOME/orca/-/blob/master/src/orca/text_attribute_names.py
+ // ATK attribute values: https://gnome.pages.gitlab.gnome.org/atk/AtkText.html#AtkTextAttribute
// https://bugzilla.gnome.org/show_bug.cgi?id=744553 "ATK docs provide no guidance for allowed values of some text attributes"
// specifically for "weight", "invalid", "language" and value range for colors
@@ -2209,6 +2263,13 @@ namespace
// (on which it produces traceback and fails to read any following text attributes),
// but that is the default value, so omit it anyway
value = QString();
+ } else if (((ia2Name == "text-line-through-style"_L1 || ia2Name == "text-line-through-type"_L1) && (ia2Value != "none"_L1))
+ || (ia2Name == "text-line-through-text"_L1 && !ia2Value.isEmpty())) {
+ // if any of the above is set, set "strikethrough" to true, but don't explicitly set
+ // to false otherwise, since any of the others might still be set to indicate strikethrough
+ // and no strikethrough is assumed anyway when nothing is explicitly set
+ name = QStringLiteral("strikethrough");
+ value = QStringLiteral("true");
} else if (ia2Name == "text-position"_L1) {
name = QStringLiteral("vertical-align");
if (value != "baseline"_L1 && value != "super"_L1 && value != "sub"_L1) {
@@ -2244,6 +2305,38 @@ namespace
}
}
+QSpiAttributeSet AtSpiAdaptor::getAttributes(QAccessibleInterface *interface) const
+{
+ QSpiAttributeSet set;
+ QAccessibleAttributesInterface *attributesIface = interface->attributesInterface();
+ if (!attributesIface)
+ return set;
+
+ const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys();
+ for (QAccessible::Attribute key : attrKeys) {
+ const QVariant value = attributesIface->attributeValue(key);
+ // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/
+ switch (key) {
+ case QAccessible::Attribute::Custom:
+ {
+ // forward custom attributes to AT-SPI as-is
+ Q_ASSERT((value.canConvert<QHash<QString, QString>>()));
+ const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>();
+ for (auto [name, val] : attrMap.asKeyValueRange())
+ set.insert(name, val);
+ break;
+ }
+ case QAccessible::Attribute::Level:
+ Q_ASSERT(value.canConvert<int>());
+ set.insert(QStringLiteral("level"), QString::number(value.toInt()));
+ break;
+ default:
+ break;
+ }
+ }
+ return set;
+}
+
// FIXME all attribute methods below should share code
QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int offset, bool includeDefaults) const
{
@@ -2256,11 +2349,13 @@ QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int of
QString joined = interface->textInterface()->attributes(offset, &startOffset, &endOffset);
const QStringList attributes = joined.split(u';', Qt::SkipEmptyParts, Qt::CaseSensitive);
for (const QString &attr : attributes) {
- QStringList items;
- items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
- AtSpiAttribute attribute = atspiTextAttribute(items[0], items[1]);
- if (!attribute.isNull())
- set[attribute.name] = attribute.value;
+ QStringList items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
+ if (items.count() == 2)
+ {
+ AtSpiAttribute attribute = atspiTextAttribute(items[0], items[1]);
+ if (!attribute.isNull())
+ set[attribute.name] = attribute.value;
+ }
}
QVariantList list;
@@ -2672,14 +2767,15 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString
if (cols > 0) {
row = index / cols;
col = index % cols;
- QAccessibleTableCellInterface *cell = interface->tableInterface()->cellAt(row, col)->tableCellInterface();
- if (cell) {
- row = cell->rowIndex();
- col = cell->columnIndex();
- rowExtents = cell->rowExtent();
- colExtents = cell->columnExtent();
- isSelected = cell->isSelected();
- success = true;
+ if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, col)) {
+ if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface()) {
+ row = cellIface->rowIndex();
+ col = cellIface->columnIndex();
+ rowExtents = cellIface->rowExtent();
+ colExtents = cellIface->columnExtent();
+ isSelected = cellIface->isSelected();
+ success = true;
+ }
}
}
QVariantList list;
@@ -2689,12 +2785,22 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString
} else if (function == "GetColumnExtentAt"_L1) {
int row = message.arguments().at(0).toInt();
int column = message.arguments().at(1).toInt();
- connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->columnExtent()));
+ int columnExtent = 0;
+ if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column)) {
+ if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface())
+ columnExtent = cellIface->columnExtent();
+ }
+ connection.send(message.createReply(columnExtent));
} else if (function == "GetRowExtentAt"_L1) {
int row = message.arguments().at(0).toInt();
int column = message.arguments().at(1).toInt();
- connection.send(message.createReply(interface->tableInterface()->cellAt(row, column)->tableCellInterface()->rowExtent()));
+ int rowExtent = 0;
+ if (QAccessibleInterface *cell = interface->tableInterface()->cellAt(row, column)) {
+ if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface())
+ rowExtent = cellIface->rowExtent();
+ }
+ connection.send(message.createReply(rowExtent));
} else if (function == "GetColumnHeader"_L1) {
int column = message.arguments().at(0).toInt();
@@ -2734,8 +2840,12 @@ bool AtSpiAdaptor::tableInterface(QAccessibleInterface *interface, const QString
} else if (function == "IsSelected"_L1) {
int row = message.arguments().at(0).toInt();
int column = message.arguments().at(1).toInt();
- QAccessibleTableCellInterface* cell = interface->tableInterface()->cellAt(row, column)->tableCellInterface();
- connection.send(message.createReply(cell->isSelected()));
+ bool selected = false;
+ if (QAccessibleInterface* cell = interface->tableInterface()->cellAt(row, column)) {
+ if (QAccessibleTableCellInterface *cellIface = cell->tableCellInterface())
+ selected = cellIface->isSelected();
+ }
+ connection.send(message.createReply(selected));
} else if (function == "AddColumnSelection"_L1) {
int column = message.arguments().at(0).toInt();
connection.send(message.createReply(interface->tableInterface()->selectColumn(column)));
@@ -2764,7 +2874,17 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt
return false;
}
- if (function == "GetColumnSpan"_L1) {
+ if (function == "GetColumnHeaderCells"_L1) {
+ QSpiObjectReferenceArray headerCells;
+ const auto headerCellInterfaces = cellInterface->columnHeaderCells();
+ headerCells.reserve(headerCellInterfaces.size());
+ for (QAccessibleInterface *cell : headerCellInterfaces) {
+ const QString childPath = pathForInterface(cell);
+ const QSpiObjectReference ref(connection, QDBusObjectPath(childPath));
+ headerCells << ref;
+ }
+ connection.send(message.createReply(QVariant::fromValue(headerCells)));
+ } else if (function == "GetColumnSpan"_L1) {
connection.send(message.createReply(QVariant::fromValue(QDBusVariant(
QVariant::fromValue(cellInterface->columnExtent())))));
} else if (function == "GetPosition"_L1) {
@@ -2772,6 +2892,16 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt
const int column = cellInterface->columnIndex();
connection.send(message.createReply(QVariant::fromValue(QDBusVariant(
QVariant::fromValue(QPoint(row, column))))));
+ } else if (function == "GetRowHeaderCells"_L1) {
+ QSpiObjectReferenceArray headerCells;
+ const auto headerCellInterfaces = cellInterface->rowHeaderCells();
+ headerCells.reserve(headerCellInterfaces.size());
+ for (QAccessibleInterface *cell : headerCellInterfaces) {
+ const QString childPath = pathForInterface(cell);
+ const QSpiObjectReference ref(connection, QDBusObjectPath(childPath));
+ headerCells << ref;
+ }
+ connection.send(message.createReply(QVariant::fromValue(headerCells)));
} else if (function == "GetRowSpan"_L1) {
connection.send(message.createReply(QVariant::fromValue(QDBusVariant(
QVariant::fromValue(cellInterface->rowExtent())))));
@@ -2785,6 +2915,9 @@ bool AtSpiAdaptor::tableCellInterface(QAccessibleInterface *interface, const QSt
if (table && table->tableInterface())
ref = QSpiObjectReference(connection, QDBusObjectPath(pathForInterface(table)));
connection.send(message.createReply(QVariant::fromValue(QDBusVariant(QVariant::fromValue(ref)))));
+ } else {
+ qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::tableCellInterface does not implement" << function << message.path();
+ return false;
}
return true;
diff --git a/src/gui/accessible/linux/atspiadaptor_p.h b/src/gui/accessible/linux/atspiadaptor_p.h
index 3a890f3d7d..68a455e7cb 100644
--- a/src/gui/accessible/linux/atspiadaptor_p.h
+++ b/src/gui/accessible/linux/atspiadaptor_p.h
@@ -16,7 +16,7 @@
// We mean it.
//
-#include <atspi/atspi-constants.h>
+#include <atspi/atspi.h>
#include <QtGui/private/qtguiglobal_p.h>
#include <QtDBus/qdbusvirtualobject.h>
@@ -85,8 +85,11 @@ private:
void notifyStateChange(QAccessibleInterface *interface, const QString& state, int value);
+ void sendAnnouncement(QAccessibleAnnouncementEvent *event);
+
// accessible helper functions
AtspiRole getRole(QAccessibleInterface *interface) const;
+ QSpiAttributeSet getAttributes(QAccessibleInterface *) const;
QSpiRelationArray relationSet(QAccessibleInterface *interface, const QDBusConnection &connection) const;
QStringList accessibleInterfaces(QAccessibleInterface *interface) const;
@@ -130,6 +133,7 @@ private:
// all of object
uint sendObject : 1;
uint sendObject_active_descendant_changed : 1;
+ uint sendObject_announcement : 1;
uint sendObject_attributes_changed : 1;
uint sendObject_bounds_changed : 1;
uint sendObject_children_changed : 1;
diff --git a/src/gui/accessible/linux/dbusconnection.cpp b/src/gui/accessible/linux/dbusconnection.cpp
index b4a8643474..10bd10927e 100644
--- a/src/gui/accessible/linux/dbusconnection.cpp
+++ b/src/gui/accessible/linux/dbusconnection.cpp
@@ -56,11 +56,13 @@ DBusConnection::DBusConnection(QObject *parent)
if (c.interface()->isServiceRegistered(A11Y_SERVICE))
serviceRegistered();
- // In addition try if there is an xatom exposing the bus address, this allows applications run as root to work
- QString address = getAddressFromXCB();
- if (!address.isEmpty()) {
- m_enabled = true;
- connectA11yBus(address);
+ if (QGuiApplication::platformName().startsWith("xcb"_L1)) {
+ // In addition try if there is an xatom exposing the bus address, this allows applications run as root to work
+ QString address = getAddressFromXCB();
+ if (!address.isEmpty()) {
+ m_enabled = true;
+ connectA11yBus(address);
+ }
}
}
diff --git a/src/gui/accessible/linux/dbusxml/Socket.xml b/src/gui/accessible/linux/dbusxml/Socket.xml
index 75ec99f994..f9ac76d2c8 100644
--- a/src/gui/accessible/linux/dbusxml/Socket.xml
+++ b/src/gui/accessible/linux/dbusxml/Socket.xml
@@ -17,7 +17,7 @@
<signal name="Available">
<arg direction="in" name="socket" type="(so)"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QSpiObjectReference"/>
- </method>
+ </signal>
</interface>
</node>
diff --git a/src/gui/accessible/linux/qspi_constant_mappings.cpp b/src/gui/accessible/linux/qspi_constant_mappings.cpp
index 4fc7bdf83c..e5b6e3f634 100644
--- a/src/gui/accessible/linux/qspi_constant_mappings.cpp
+++ b/src/gui/accessible/linux/qspi_constant_mappings.cpp
@@ -36,6 +36,8 @@ quint64 spiStatesFromQState(QAccessible::State state)
setSpiStateBit(&spiState, ATSPI_STATE_FOCUSED);
if (state.pressed)
setSpiStateBit(&spiState, ATSPI_STATE_PRESSED);
+ if (state.checkable)
+ setSpiStateBit(&spiState, ATSPI_STATE_CHECKABLE);
if (state.checked)
setSpiStateBit(&spiState, ATSPI_STATE_CHECKED);
if (state.checkStateMixed)
@@ -75,7 +77,8 @@ quint64 spiStatesFromQState(QAccessible::State state)
if (state.extSelectable)
setSpiStateBit(&spiState, ATSPI_STATE_SELECTABLE);
// if (state.Protected)
- // if (state.HasPopup)
+ if (state.hasPopup)
+ setSpiStateBit(&spiState, ATSPI_STATE_HAS_POPUP);
if (state.modal)
setSpiStateBit(&spiState, ATSPI_STATE_MODAL);
if (state.multiLine)
diff --git a/src/gui/accessible/linux/qspiaccessiblebridge.cpp b/src/gui/accessible/linux/qspiaccessiblebridge.cpp
index f59d8be18b..de2e7d5fc0 100644
--- a/src/gui/accessible/linux/qspiaccessiblebridge.cpp
+++ b/src/gui/accessible/linux/qspiaccessiblebridge.cpp
@@ -38,7 +38,7 @@ QSpiAccessibleBridge::QSpiAccessibleBridge()
// But do that only on next loop, once dbus is really settled.
QTimer::singleShot(
0, this, [this]{
- if (dbusConnection->isEnabled())
+ if (dbusConnection->isEnabled() && dbusConnection->connection().isConnected())
enabledChanged(true);
});
}
@@ -205,7 +205,11 @@ static RoleMapping map[] = {
//: Role of an accessible object
{ QAccessible::ButtonDropDown, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down") },
//: Role of an accessible object
+#if ATSPI_ROLE_COUNT > 130
+ { QAccessible::ButtonMenu, ATSPI_ROLE_PUSH_BUTTON_MENU, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button menu") },
+#else
{ QAccessible::ButtonMenu, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button menu") },
+#endif
//: Role of an accessible object - a button that expands a grid.
{ QAccessible::ButtonDropGrid, ATSPI_ROLE_PUSH_BUTTON, QT_TRANSLATE_NOOP("QSpiAccessibleBridge", "button with drop down grid") },
//: Role of an accessible object - blank space between other objects.
diff --git a/src/gui/accessible/linux/qspiapplicationadaptor.cpp b/src/gui/accessible/linux/qspiapplicationadaptor.cpp
index 963490d056..37d7648984 100644
--- a/src/gui/accessible/linux/qspiapplicationadaptor.cpp
+++ b/src/gui/accessible/linux/qspiapplicationadaptor.cpp
@@ -12,12 +12,16 @@
#include "deviceeventcontroller_adaptor.h"
#include "atspi/atspi-constants.h"
+#if __has_include(<xcb/xproto.h>)
#include <xcb/xproto.h>
+#endif
//#define KEYBOARD_DEBUG
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
/*!
\class QSpiApplicationAdaptor
\internal
@@ -125,9 +129,13 @@ bool QSpiApplicationAdaptor::eventFilter(QObject *target, QEvent *event)
de.modifiers = 0;
if ((keyEvent->modifiers() & Qt::ShiftModifier) && (keyEvent->key() != Qt::Key_Shift))
de.modifiers |= 1 << ATSPI_MODIFIER_SHIFT;
- // TODO rather introduce Qt::CapslockModifier into KeyboardModifier
- if (keyEvent->nativeModifiers() & XCB_MOD_MASK_LOCK )
- de.modifiers |= 1 << ATSPI_MODIFIER_SHIFTLOCK;
+#ifdef XCB_MOD_MASK_LOCK
+ if (QGuiApplication::platformName().startsWith("xcb"_L1)) {
+ // TODO rather introduce Qt::CapslockModifier into KeyboardModifier
+ if (keyEvent->nativeModifiers() & XCB_MOD_MASK_LOCK )
+ de.modifiers |= 1 << ATSPI_MODIFIER_SHIFTLOCK;
+ }
+#endif
if ((keyEvent->modifiers() & Qt::ControlModifier) && (keyEvent->key() != Qt::Key_Control))
de.modifiers |= 1 << ATSPI_MODIFIER_CONTROL;
if ((keyEvent->modifiers() & Qt::AltModifier) && (keyEvent->key() != Qt::Key_Alt))
diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp
index 583abf5302..eec779efb1 100644
--- a/src/gui/accessible/qaccessible.cpp
+++ b/src/gui/accessible/qaccessible.cpp
@@ -173,6 +173,7 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value ActionChanged An action has been changed.
\value ActiveDescendantChanged
\value Alert A system alert (e.g., a message from a QMessageBox)
+ \value [since 6.8] Announcement The announcement of a message is requested.
\value AttributeChanged
\value ContextHelpEnd Context help (QWhatsThis) for an object is finished.
\value ContextHelpStart Context help (QWhatsThis) for an object is initiated.
@@ -412,6 +413,67 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\sa QAccessibleTextInterface
*/
+/*! \enum QAccessible::Attribute
+ This enum describes different types of attributes used by the
+ \l QAccessibleAttributesInterface.
+ \since 6.8
+
+ These attributes are comparable to the concept of properties/(object)
+ attributes found in ARIA, AT-SPI2, IAccessible, UIA and NSAccessibility
+ and are mapped to their platform counterpart where applicable.
+
+ Each attribute is handled as a key-value pair, with the values of this
+ enumeration being used as keys.
+
+ Attribute values are represented in a \l QVariant. The type of the value
+ stored in the \l QVariant is fixed and specified below for each of the
+ attribute types.
+
+ \value Custom value type: \a QHash<QString, QString>
+ The \a Custom attribute is special in that
+ it can effectively represent multiple attributes at
+ once, since it itself is a \l QHash used to represent
+ key-value pairs.
+ For platforms supporting custom key-value pairs for
+ attributes, those set in the \a Custom attribute
+ are bridged to the platform layer without applying any
+ translation to platform-specific attributes. In general,
+ the other, more strongly typed attributes should be used.
+ This attribute can e.g. be used for prototyping
+ before officially adding an official new enumeration value
+ for a specific feature.
+ \value Level value type: \a int
+ Defines the hierarchical level of an element within a structure,
+ e.g. the heading level of a heading. This attribute conceptually
+ matches the "aria-level" property in ARIA.
+
+ \sa QAccessibleAttributesInterface
+*/
+
+/*! \enum QAccessible::AnnouncementPriority
+ This enum describes the priority for announcements used by the
+ \l QAccessibleAnnouncementEvent.
+ \since 6.8
+
+ With \a QAccessible::AnouncementPriority::Polite, assistive technologies
+ should announce the message at the next graceful opportunity such as at the
+ end of speaking the current sentence or when the user pauses typing.
+
+ When specifying \a QAccessible::AnouncementPriority::Assertive, assistive
+ technologies should notify the user immediately.
+
+ Because an interruption might disorient users or cause them to not complete
+ their current task, \a QAccessible::AnouncementPriority::Assertive should
+ not be used unless the interruption is imperative.
+
+ \value Polite The announcement has normal priority.
+ \value Assertive The announcement has high priority and should notify
+ the user immediately, even if that means interrupting the user's
+ current task.
+
+ \sa QAccessibleAnnouncementEvent
+*/
+
/*!
\enum QAccessible::InterfaceType
@@ -431,8 +493,9 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value TableCellInterface For cells in a TableInterface object.
\value HyperlinkInterface For hyperlink nodes (usually embedded as children of text nodes)
\value [since 6.5] SelectionInterface For non-text objects that support selection of child objects.
+ \value [since 6.8] AttributesInterface For objects that support object-specific attributes.
- \sa QAccessibleInterface::interface_cast(), QAccessibleTextInterface, QAccessibleValueInterface, QAccessibleActionInterface, QAccessibleTableInterface, QAccessibleTableCellInterface, QAccessibleSelectionInterface
+ \sa QAccessibleInterface::interface_cast(), QAccessibleTextInterface, QAccessibleValueInterface, QAccessibleActionInterface, QAccessibleTableInterface, QAccessibleTableCellInterface, QAccessibleSelectionInterface, QAccessibleAttributesInterface
*/
#if QT_CONFIG(accessibility)
@@ -1655,8 +1718,8 @@ QAccessibleTextRemoveEvent::~QAccessibleTextRemoveEvent()
/*!
\fn QAccessibleTextInsertEvent::QAccessibleTextInsertEvent(QAccessibleInterface *iface, int position, const QString &text)
- Constructs a new QAccessibleTextInsertEvent event for \a iface. The text has been inserted at
- \a position.
+ Constructs a new QAccessibleTextInsertEvent event for \a iface. The \a text has been inserted
+ at \a position.
*/
/*!
@@ -1740,9 +1803,58 @@ QAccessibleTextSelectionEvent::~QAccessibleTextSelectionEvent()
{
}
+/*!
+ \since 6.8
+ \class QAccessibleAnnouncementEvent
+ \ingroup accessibility
+ \inmodule QtGui
+
+ \brief The QAccessibleAnnouncementEvent is used to request the announcement
+ of a given message by assistive technologies.
+
+ This class is used with \l QAccessible::updateAccessibility().
+*/
+
+/*! \fn QAccessibleAnnouncementEvent::QAccessibleAnnouncementEvent(QObject *object, const QString &message)
+
+ Constructs a new QAccessibleAnnouncementEvent event for \a object
+ to request the announcement of \a message with priority \l QAccessible::AnnouncementPriority::Polite.
+
+ \l QAccessibleAnnouncementEvent::setPriority can be used to adjust the priority.
+*/
+
+/*! \fn QAccessibleAnnouncementEvent::QAccessibleAnnouncementEvent(QAccessibleInterface *iface, const QString &message)
+
+ Constructs a new QAccessibleAnnouncementEvent event for \a iface
+ to request the announcement of \a message with priority \l QAccessible::AnnouncementPriority::Polite.
+
+ \l QAccessibleAnnouncementEvent::setPriority can be used to adjust the priority.
+*/
+
+/*! \fn QString QAccessibleAnnouncementEvent::message() const
+
+ Returns the message.
+*/
+
+/*! \fn QAccessible::AnnouncementPriority QAccessibleAnnouncementEvent::priority() const
+
+ Returns the priority.
+*/
+
+/*! \fn void QAccessibleAnnouncementEvent::setPriority(QAccessible::AnnouncementPriority priority)
+
+ Sets the priority with which the announcement will be requested to \a priority.
+*/
/*!
+ \internal
+*/
+QAccessibleAnnouncementEvent::~QAccessibleAnnouncementEvent()
+{
+}
+
+/*!
Returns the QAccessibleInterface associated with the event.
*/
QAccessibleInterface *QAccessibleEvent::accessibleInterface() const
@@ -2968,7 +3080,6 @@ QString QAccessibleActionInterface::nextPageAction()
\class QAccessibleSelectionInterface
\inmodule QtGui
\ingroup accessibility
- \preliminary
\brief The QAccessibleSelectionInterface class implements support for
selection handling.
@@ -3070,6 +3181,57 @@ bool QAccessibleSelectionInterface::isSelected(QAccessibleInterface *childItem)
*/
+/*!
+ \since 6.8
+ \class QAccessibleAttributesInterface
+ \inmodule QtGui
+ \ingroup accessibility
+
+ \brief The QAccessibleAttributesInterface class implements support for
+ reporting attributes for an accessible object.
+
+ Attributes are key-value pairs. Values are stored in \l QVariant.
+
+ The \l QAccessible::Attribute enumeration describes the available keys and
+ documents which type to use for the value of each key.
+
+ While the text-specific attributes handled by \l QAccessibleTextInterface::attributes
+ are specific to objects implementing text and are specific to a specific text
+ position/offset, the attributes handled by the \l QAccessibleAttributesInterface
+ can be used for objects of any role and apply for the whole object.
+
+ Classes already implementing \l QAccessibleTextInterface for text-specific attrtibutes
+ may want to implement \l QAccessibleAttributesInterface in addition for object-specific
+ attributes.
+*/
+
+/*!
+
+ Destroys the QAccessibleAttributesInterface.
+*/
+QAccessibleAttributesInterface::~QAccessibleAttributesInterface()
+{
+}
+
+/*!
+ \fn QList<QAccessible::Attribute> QAccessibleAttributesInterface::attributeKeys() const
+
+ Returns the keys of all attributes the object supports. The \l QAccessible::Attribute
+ enumeration describes available keys.
+*/
+
+/*!
+ \fn QVariant QAccessibleAttributesInterface::attributeValue(QAccessible::Attribute key) const
+
+ Returns the value of the attribute \a key of this object.
+
+ If the attribute is set for this object, a value of the type documented for the
+ given key in the documentation of the \l QAccessible::Attribute enumeration is
+ returned in the \l QVariant.
+
+ Otherwise, an invalid \l QVariant is returned.
+*/
+
/*! \internal */
QString qAccessibleLocalizedActionDescription(const QString &actionName)
{
diff --git a/src/gui/accessible/qaccessible.h b/src/gui/accessible/qaccessible.h
index 3fdc4eb7d9..3d8daa4b3c 100644
--- a/src/gui/accessible/qaccessible.h
+++ b/src/gui/accessible/qaccessible.h
@@ -43,6 +43,7 @@ class QAccessibleTableInterface;
class QAccessibleTableCellInterface;
class QAccessibleHyperlinkInterface;
class QAccessibleSelectionInterface;
+class QAccessibleAttributesInterface;
class QAccessibleTableModelChangeEvent;
class Q_GUI_EXPORT QAccessibleInterface
@@ -106,6 +107,9 @@ public:
inline QAccessibleSelectionInterface *selectionInterface()
{ return reinterpret_cast<QAccessibleSelectionInterface *>(interface_cast(QAccessible::SelectionInterface)); }
+ inline QAccessibleAttributesInterface *attributesInterface()
+ { return reinterpret_cast<QAccessibleAttributesInterface *>(interface_cast(QAccessible::AttributesInterface)); }
+
virtual void virtual_hook(int id, void *data);
virtual void *interface_cast(QAccessible::InterfaceType)
@@ -284,6 +288,15 @@ public:
virtual bool clear() = 0;
};
+class Q_GUI_EXPORT QAccessibleAttributesInterface
+{
+public:
+ virtual ~QAccessibleAttributesInterface();
+ virtual QList<QAccessible::Attribute> attributeKeys() const = 0;
+ virtual QVariant attributeValue(QAccessible::Attribute key) const = 0;
+};
+
+
class Q_GUI_EXPORT QAccessibleEvent
{
Q_DISABLE_COPY(QAccessibleEvent)
@@ -303,6 +316,7 @@ public:
Q_ASSERT(m_type != QAccessible::TextRemoved);
Q_ASSERT(m_type != QAccessible::TextUpdated);
Q_ASSERT(m_type != QAccessible::TableModelChanged);
+ Q_ASSERT(m_type != QAccessible::Announcement);
}
inline QAccessibleEvent(QAccessibleInterface *iface, QAccessible::Event typ)
@@ -317,6 +331,7 @@ public:
Q_ASSERT(m_type != QAccessible::TextRemoved);
Q_ASSERT(m_type != QAccessible::TextUpdated);
Q_ASSERT(m_type != QAccessible::TableModelChanged);
+ Q_ASSERT(m_type != QAccessible::Announcement);
m_uniqueId = QAccessible::uniqueId(iface);
m_object = iface->object();
}
@@ -592,6 +607,36 @@ protected:
int m_lastColumn;
};
+class Q_GUI_EXPORT QAccessibleAnnouncementEvent : public QAccessibleEvent
+{
+public:
+ inline QAccessibleAnnouncementEvent(QObject *object, const QString &message)
+ : QAccessibleEvent(object, QAccessible::InvalidEvent)
+ , m_message(message)
+ , m_priority(QAccessible::AnnouncementPriority::Polite)
+ {
+ m_type = QAccessible::Announcement;
+ }
+
+ inline QAccessibleAnnouncementEvent(QAccessibleInterface *iface, const QString &message)
+ : QAccessibleEvent(iface, QAccessible::InvalidEvent)
+ , m_message(message)
+ , m_priority(QAccessible::AnnouncementPriority::Polite)
+ {
+ m_type = QAccessible::Announcement;
+ }
+
+ ~QAccessibleAnnouncementEvent();
+
+ QString message() const { return m_message; }
+ QAccessible::AnnouncementPriority priority() const { return m_priority; }
+ void setPriority(QAccessible::AnnouncementPriority priority) { m_priority = priority; };
+
+protected:
+ QString m_message;
+ QAccessible::AnnouncementPriority m_priority;
+};
+
#ifndef Q_QDOC
#define QAccessibleInterface_iid "org.qt-project.Qt.QAccessibleInterface"
Q_DECLARE_INTERFACE(QAccessibleInterface, QAccessibleInterface_iid)
diff --git a/src/gui/accessible/qaccessible_base.h b/src/gui/accessible/qaccessible_base.h
index 74926a3565..1ca3dadc36 100644
--- a/src/gui/accessible/qaccessible_base.h
+++ b/src/gui/accessible/qaccessible_base.h
@@ -101,6 +101,7 @@ public:
HelpChanged = 0x80A0,
DefaultActionChanged = 0x80B0,
AcceleratorChanged = 0x80C0,
+ Announcement = 0x80D0,
InvalidEvent
};
@@ -349,7 +350,8 @@ public:
TableInterface,
TableCellInterface,
HyperlinkInterface,
- SelectionInterface
+ SelectionInterface,
+ AttributesInterface,
};
enum TextBoundaryType {
@@ -361,6 +363,17 @@ public:
NoBoundary
};
+ enum class Attribute {
+ Custom,
+ Level,
+ };
+
+ enum class AnnouncementPriority {
+ Polite,
+ Assertive
+ };
+ Q_ENUM(AnnouncementPriority)
+
typedef QAccessibleInterface*(*InterfaceFactory)(const QString &key, QObject*);
typedef void(*UpdateHandler)(QAccessibleEvent *event);
typedef void(*RootObjectHandler)(QObject*);
diff --git a/src/gui/accessible/qaccessiblebridge.cpp b/src/gui/accessible/qaccessiblebridge.cpp
index a8b54b0c68..0651c516ea 100644
--- a/src/gui/accessible/qaccessiblebridge.cpp
+++ b/src/gui/accessible/qaccessiblebridge.cpp
@@ -33,6 +33,8 @@ QT_BEGIN_NAMESPACE
Destroys the accessibility bridge object.
*/
+QAccessibleBridge::~QAccessibleBridge()
+ = default;
/*!
\fn void QAccessibleBridge::setRootObject(QAccessibleInterface *object)
diff --git a/src/gui/accessible/qaccessiblebridge.h b/src/gui/accessible/qaccessiblebridge.h
index 4d817c8e58..753f59597a 100644
--- a/src/gui/accessible/qaccessiblebridge.h
+++ b/src/gui/accessible/qaccessiblebridge.h
@@ -16,10 +16,10 @@ QT_BEGIN_NAMESPACE
class QAccessibleInterface;
class QAccessibleEvent;
-class QAccessibleBridge
+class Q_GUI_EXPORT QAccessibleBridge
{
public:
- virtual ~QAccessibleBridge() {}
+ virtual ~QAccessibleBridge();
virtual void setRootObject(QAccessibleInterface *) = 0;
virtual void notifyAccessibilityUpdate(QAccessibleEvent *event) = 0;
};
diff --git a/src/gui/accessible/windows/apisupport/qwindowsuiawrapper.cpp b/src/gui/accessible/windows/apisupport/qwindowsuiawrapper.cpp
deleted file mode 100644
index d9ff723a61..0000000000
--- a/src/gui/accessible/windows/apisupport/qwindowsuiawrapper.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include <initguid.h>
-
-#include "qwindowsuiawrapper_p.h"
-#include <QtCore/private/qsystemlibrary_p.h>
-
-QT_BEGIN_NAMESPACE
-
-// private constructor
-QWindowsUiaWrapper::QWindowsUiaWrapper()
-{
- QSystemLibrary uiaLib(QStringLiteral("UIAutomationCore"));
- if (uiaLib.load()) {
- m_pUiaReturnRawElementProvider = reinterpret_cast<PtrUiaReturnRawElementProvider>(uiaLib.resolve("UiaReturnRawElementProvider"));
- m_pUiaHostProviderFromHwnd = reinterpret_cast<PtrUiaHostProviderFromHwnd>(uiaLib.resolve("UiaHostProviderFromHwnd"));
- m_pUiaRaiseAutomationPropertyChangedEvent = reinterpret_cast<PtrUiaRaiseAutomationPropertyChangedEvent>(uiaLib.resolve("UiaRaiseAutomationPropertyChangedEvent"));
- m_pUiaRaiseAutomationEvent = reinterpret_cast<PtrUiaRaiseAutomationEvent>(uiaLib.resolve("UiaRaiseAutomationEvent"));
- m_pUiaRaiseNotificationEvent = reinterpret_cast<PtrUiaRaiseNotificationEvent>(uiaLib.resolve("UiaRaiseNotificationEvent"));
- m_pUiaClientsAreListening = reinterpret_cast<PtrUiaClientsAreListening>(uiaLib.resolve("UiaClientsAreListening"));
- }
-}
-
-QWindowsUiaWrapper::~QWindowsUiaWrapper()
-{
-}
-
-// shared instance
-QWindowsUiaWrapper *QWindowsUiaWrapper::instance()
-{
- static QWindowsUiaWrapper wrapper;
- return &wrapper;
-}
-
-// True if most symbols resolved (UiaRaiseNotificationEvent is optional).
-BOOL QWindowsUiaWrapper::ready()
-{
- return m_pUiaReturnRawElementProvider
- && m_pUiaHostProviderFromHwnd
- && m_pUiaRaiseAutomationPropertyChangedEvent
- && m_pUiaRaiseAutomationEvent
- && m_pUiaClientsAreListening;
-}
-
-BOOL QWindowsUiaWrapper::clientsAreListening()
-{
- if (!m_pUiaClientsAreListening)
- return FALSE;
- return m_pUiaClientsAreListening();
-}
-
-LRESULT QWindowsUiaWrapper::returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el)
-{
- if (!m_pUiaReturnRawElementProvider)
- return static_cast<LRESULT>(NULL);
- return m_pUiaReturnRawElementProvider(hwnd, wParam, lParam, el);
-}
-
-HRESULT QWindowsUiaWrapper::hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider)
-{
- if (!m_pUiaHostProviderFromHwnd)
- return UIA_E_NOTSUPPORTED;
- return m_pUiaHostProviderFromHwnd(hwnd, ppProvider);
-}
-
-HRESULT QWindowsUiaWrapper::raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue)
-{
- if (!m_pUiaRaiseAutomationPropertyChangedEvent)
- return UIA_E_NOTSUPPORTED;
- return m_pUiaRaiseAutomationPropertyChangedEvent(pProvider, id, oldValue, newValue);
-}
-
-HRESULT QWindowsUiaWrapper::raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id)
-{
- if (!m_pUiaRaiseAutomationEvent)
- return UIA_E_NOTSUPPORTED;
- return m_pUiaRaiseAutomationEvent(pProvider, id);
-}
-
-HRESULT QWindowsUiaWrapper::raiseNotificationEvent(IRawElementProviderSimple *provider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId)
-{
- if (!m_pUiaRaiseNotificationEvent)
- return UIA_E_NOTSUPPORTED;
- return m_pUiaRaiseNotificationEvent(provider, notificationKind, notificationProcessing, displayString, activityId);
-}
-
-QT_END_NAMESPACE
-
diff --git a/src/gui/accessible/windows/apisupport/qwindowsuiawrapper_p.h b/src/gui/accessible/windows/apisupport/qwindowsuiawrapper_p.h
deleted file mode 100644
index 05b93f8393..0000000000
--- a/src/gui/accessible/windows/apisupport/qwindowsuiawrapper_p.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWINDOWSUIAWRAPPER_H
-#define QWINDOWSUIAWRAPPER_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of other Qt classes. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtGui/private/qtguiglobal_p.h>
-
-#include "uiatypes_p.h"
-#include "uiaattributeids_p.h"
-#include "uiacontroltypeids_p.h"
-#include "uiaerrorids_p.h"
-#include "uiaeventids_p.h"
-#include "uiageneralids_p.h"
-#include "uiapatternids_p.h"
-#include "uiapropertyids_p.h"
-#include "uiaserverinterfaces_p.h"
-#include "uiaclientinterfaces_p.h"
-
-QT_REQUIRE_CONFIG(accessibility);
-
-QT_BEGIN_NAMESPACE
-
-class Q_GUI_EXPORT QWindowsUiaWrapper
-{
- QWindowsUiaWrapper();
- virtual ~QWindowsUiaWrapper();
-public:
- static QWindowsUiaWrapper *instance();
- BOOL ready();
- BOOL clientsAreListening();
- LRESULT returnRawElementProvider(HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el);
- HRESULT hostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider);
- HRESULT raiseAutomationPropertyChangedEvent(IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue);
- HRESULT raiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id);
- HRESULT raiseNotificationEvent(IRawElementProviderSimple *provider, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityId);
-
-private:
- typedef LRESULT (WINAPI *PtrUiaReturnRawElementProvider)(HWND, WPARAM, LPARAM, IRawElementProviderSimple *);
- typedef HRESULT (WINAPI *PtrUiaHostProviderFromHwnd)(HWND, IRawElementProviderSimple **);
- typedef HRESULT (WINAPI *PtrUiaRaiseAutomationPropertyChangedEvent)(IRawElementProviderSimple *, PROPERTYID, VARIANT, VARIANT);
- typedef HRESULT (WINAPI *PtrUiaRaiseAutomationEvent)(IRawElementProviderSimple *, EVENTID);
- typedef HRESULT (WINAPI *PtrUiaRaiseNotificationEvent)(IRawElementProviderSimple *, NotificationKind, NotificationProcessing, BSTR, BSTR);
- typedef BOOL (WINAPI *PtrUiaClientsAreListening)();
- PtrUiaReturnRawElementProvider m_pUiaReturnRawElementProvider = nullptr;
- PtrUiaHostProviderFromHwnd m_pUiaHostProviderFromHwnd = nullptr;
- PtrUiaRaiseAutomationPropertyChangedEvent m_pUiaRaiseAutomationPropertyChangedEvent = nullptr;
- PtrUiaRaiseAutomationEvent m_pUiaRaiseAutomationEvent = nullptr;
- PtrUiaRaiseNotificationEvent m_pUiaRaiseNotificationEvent = nullptr;
- PtrUiaClientsAreListening m_pUiaClientsAreListening = nullptr;
-};
-
-QT_END_NAMESPACE
-
-#endif //QWINDOWSUIAWRAPPER_H
-
diff --git a/src/gui/accessible/windows/apisupport/uiaattributeids_p.h b/src/gui/accessible/windows/apisupport/uiaattributeids_p.h
deleted file mode 100644
index 2078351a98..0000000000
--- a/src/gui/accessible/windows/apisupport/uiaattributeids_p.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAATTRIBUTEIDS_H
-#define UIAATTRIBUTEIDS_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.
-//
-
-#define UIA_AnimationStyleAttributeId 40000
-#define UIA_BackgroundColorAttributeId 40001
-#define UIA_BulletStyleAttributeId 40002
-#define UIA_CapStyleAttributeId 40003
-#define UIA_CultureAttributeId 40004
-#define UIA_FontNameAttributeId 40005
-#define UIA_FontSizeAttributeId 40006
-#define UIA_FontWeightAttributeId 40007
-#define UIA_ForegroundColorAttributeId 40008
-#define UIA_HorizontalTextAlignmentAttributeId 40009
-#define UIA_IndentationFirstLineAttributeId 40010
-#define UIA_IndentationLeadingAttributeId 40011
-#define UIA_IndentationTrailingAttributeId 40012
-#define UIA_IsHiddenAttributeId 40013
-#define UIA_IsItalicAttributeId 40014
-#define UIA_IsReadOnlyAttributeId 40015
-#define UIA_IsSubscriptAttributeId 40016
-#define UIA_IsSuperscriptAttributeId 40017
-#define UIA_MarginBottomAttributeId 40018
-#define UIA_MarginLeadingAttributeId 40019
-#define UIA_MarginTopAttributeId 40020
-#define UIA_MarginTrailingAttributeId 40021
-#define UIA_OutlineStylesAttributeId 40022
-#define UIA_OverlineColorAttributeId 40023
-#define UIA_OverlineStyleAttributeId 40024
-#define UIA_StrikethroughColorAttributeId 40025
-#define UIA_StrikethroughStyleAttributeId 40026
-#define UIA_TabsAttributeId 40027
-#define UIA_TextFlowDirectionsAttributeId 40028
-#define UIA_UnderlineColorAttributeId 40029
-#define UIA_UnderlineStyleAttributeId 40030
-#define UIA_AnnotationTypesAttributeId 40031
-#define UIA_AnnotationObjectsAttributeId 40032
-#define UIA_StyleNameAttributeId 40033
-#define UIA_StyleIdAttributeId 40034
-#define UIA_LinkAttributeId 40035
-#define UIA_IsActiveAttributeId 40036
-#define UIA_SelectionActiveEndAttributeId 40037
-#define UIA_CaretPositionAttributeId 40038
-#define UIA_CaretBidiModeAttributeId 40039
-#define UIA_LineSpacingAttributeId 40040
-#define UIA_BeforeParagraphSpacingAttributeId 40041
-#define UIA_AfterParagraphSpacingAttributeId 40042
-#define UIA_SayAsInterpretAsAttributeId 40043
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h b/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h
deleted file mode 100644
index fb74042bfa..0000000000
--- a/src/gui/accessible/windows/apisupport/uiaclientinterfaces_p.h
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIACLIENTINTERFACES_H
-#define UIACLIENTINTERFACES_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 <unknwn.h>
-
-#ifndef __IUIAutomationElement_INTERFACE_DEFINED__
-
-struct IUIAutomationCondition;
-struct IUIAutomationCacheRequest;
-struct IUIAutomationElementArray;
-struct IUIAutomationTreeWalker;
-struct IUIAutomationEventHandler;
-struct IUIAutomationPropertyChangedEventHandler;
-struct IUIAutomationStructureChangedEventHandler;
-struct IUIAutomationFocusChangedEventHandler;
-struct IUIAutomationProxyFactory;
-struct IUIAutomationProxyFactoryEntry;
-struct IUIAutomationProxyFactoryMapping;
-#ifndef __IAccessible_FWD_DEFINED__
-#define __IAccessible_FWD_DEFINED__
-struct IAccessible;
-#endif /* __IAccessible_FWD_DEFINED__ */
-
-#define __IUIAutomationElement_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IUIAutomationElement, 0xd22108aa, 0x8ac5, 0x49a5, 0x83,0x7b, 0x37,0xbb,0xb3,0xd7,0x59,0x1e);
-MIDL_INTERFACE("d22108aa-8ac5-49a5-837b-37bbb3d7591e")
-IUIAutomationElement : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE SetFocus() = 0;
- virtual HRESULT STDMETHODCALLTYPE GetRuntimeId(__RPC__deref_out_opt SAFEARRAY **runtimeId) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindFirst(enum TreeScope scope, __RPC__in_opt IUIAutomationCondition *condition, __RPC__deref_out_opt IUIAutomationElement **found) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindAll(enum TreeScope scope, __RPC__in_opt IUIAutomationCondition *condition, __RPC__deref_out_opt IUIAutomationElementArray **found) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindFirstBuildCache(enum TreeScope scope, __RPC__in_opt IUIAutomationCondition *condition, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **found) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindAllBuildCache(enum TreeScope scope, __RPC__in_opt IUIAutomationCondition *condition, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElementArray **found) = 0;
- virtual HRESULT STDMETHODCALLTYPE BuildUpdatedCache(__RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **updatedElement) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCurrentPropertyValue(PROPERTYID propertyId, __RPC__out VARIANT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCurrentPropertyValueEx(PROPERTYID propertyId, BOOL ignoreDefaultValue, __RPC__out VARIANT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedPropertyValue(PROPERTYID propertyId, __RPC__out VARIANT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedPropertyValueEx(PROPERTYID propertyId, BOOL ignoreDefaultValue, __RPC__out VARIANT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCurrentPatternAs(PATTERNID patternId, __RPC__in REFIID riid, __RPC__deref_out_opt void **patternObject) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedPatternAs(PATTERNID patternId, __RPC__in REFIID riid, __RPC__deref_out_opt void **patternObject) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCurrentPattern(PATTERNID patternId, __RPC__deref_out_opt IUnknown **patternObject) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedPattern(PATTERNID patternId, __RPC__deref_out_opt IUnknown **patternObject) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedParent(__RPC__deref_out_opt IUIAutomationElement **parent) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCachedChildren(__RPC__deref_out_opt IUIAutomationElementArray **children) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentProcessId(__RPC__out int *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentControlType(__RPC__out CONTROLTYPEID *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentLocalizedControlType(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentName(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentAcceleratorKey(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentAccessKey(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentHasKeyboardFocus(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsKeyboardFocusable(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsEnabled(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentAutomationId(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentClassName(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentHelpText(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentCulture(__RPC__out int *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsControlElement(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsContentElement(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsPassword(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentNativeWindowHandle(__RPC__deref_out_opt UIA_HWND *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentItemType(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsOffscreen(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentOrientation(__RPC__out enum OrientationType *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentFrameworkId(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsRequiredForForm(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentItemStatus(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentBoundingRectangle(__RPC__out RECT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentLabeledBy(__RPC__deref_out_opt IUIAutomationElement **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentAriaRole(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentAriaProperties(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentIsDataValidForForm(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentControllerFor(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentDescribedBy(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentFlowsTo(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CurrentProviderDescription(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedProcessId(__RPC__out int *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedControlType(__RPC__out CONTROLTYPEID *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedLocalizedControlType(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedName(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedAcceleratorKey(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedAccessKey(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedHasKeyboardFocus(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsKeyboardFocusable(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsEnabled(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedAutomationId(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedClassName(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedHelpText(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedCulture(__RPC__out int *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsControlElement(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsContentElement(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsPassword(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedNativeWindowHandle(__RPC__deref_out_opt UIA_HWND *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedItemType(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsOffscreen(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedOrientation(__RPC__out enum OrientationType *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedFrameworkId(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsRequiredForForm(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedItemStatus(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedBoundingRectangle(__RPC__out RECT *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedLabeledBy(__RPC__deref_out_opt IUIAutomationElement **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedAriaRole(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedAriaProperties(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedIsDataValidForForm(__RPC__out BOOL *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedControllerFor(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedDescribedBy(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedFlowsTo(__RPC__deref_out_opt IUIAutomationElementArray **retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CachedProviderDescription(__RPC__deref_out_opt BSTR *retVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetClickablePoint(__RPC__out POINT *clickable, __RPC__out BOOL *gotClickable) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IUIAutomationElement, 0xd22108aa, 0x8ac5, 0x49a5, 0x83,0x7b, 0x37,0xbb,0xb3,0xd7,0x59,0x1e)
-#endif
-#endif
-
-
-#ifndef __IUIAutomation_INTERFACE_DEFINED__
-#define __IUIAutomation_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IUIAutomation, 0x30cbe57d, 0xd9d0, 0x452a, 0xab,0x13, 0x7a,0xc5,0xac,0x48,0x25,0xee);
-MIDL_INTERFACE("30cbe57d-d9d0-452a-ab13-7ac5ac4825ee")
-IUIAutomation : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE CompareElements(__RPC__in_opt IUIAutomationElement *el1, __RPC__in_opt IUIAutomationElement *el2, __RPC__out BOOL *areSame) = 0;
- virtual HRESULT STDMETHODCALLTYPE CompareRuntimeIds(__RPC__in SAFEARRAY * runtimeId1, __RPC__in SAFEARRAY * runtimeId2, __RPC__out BOOL *areSame) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetRootElement(__RPC__deref_out_opt IUIAutomationElement **root) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromHandle(__RPC__in UIA_HWND hwnd, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromPoint(POINT pt, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetFocusedElement(__RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetRootElementBuildCache(__RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **root) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromHandleBuildCache(__RPC__in UIA_HWND hwnd, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromPointBuildCache(POINT pt, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetFocusedElementBuildCache(__RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateTreeWalker(__RPC__in_opt IUIAutomationCondition *pCondition, __RPC__deref_out_opt IUIAutomationTreeWalker **walker) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ControlViewWalker(__RPC__deref_out_opt IUIAutomationTreeWalker **walker) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ContentViewWalker(__RPC__deref_out_opt IUIAutomationTreeWalker **walker) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_RawViewWalker(__RPC__deref_out_opt IUIAutomationTreeWalker **walker) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_RawViewCondition(__RPC__deref_out_opt IUIAutomationCondition **condition) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ControlViewCondition(__RPC__deref_out_opt IUIAutomationCondition **condition) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ContentViewCondition(__RPC__deref_out_opt IUIAutomationCondition **condition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateCacheRequest(__RPC__deref_out_opt IUIAutomationCacheRequest **cacheRequest) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateTrueCondition(__RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateFalseCondition(__RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreatePropertyCondition(PROPERTYID propertyId, VARIANT value, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreatePropertyConditionEx(PROPERTYID propertyId, VARIANT value, enum PropertyConditionFlags flags, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateAndCondition(__RPC__in_opt IUIAutomationCondition *condition1, __RPC__in_opt IUIAutomationCondition *condition2, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateAndConditionFromArray(__RPC__in_opt SAFEARRAY * conditions, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateAndConditionFromNativeArray(__RPC__in_ecount_full(conditionCount) IUIAutomationCondition **conditions, int conditionCount, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateOrCondition(__RPC__in_opt IUIAutomationCondition *condition1, __RPC__in_opt IUIAutomationCondition *condition2, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateOrConditionFromArray(__RPC__in_opt SAFEARRAY * conditions, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateOrConditionFromNativeArray(__RPC__in_ecount_full(conditionCount) IUIAutomationCondition **conditions, int conditionCount, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateNotCondition(__RPC__in_opt IUIAutomationCondition *condition, __RPC__deref_out_opt IUIAutomationCondition **newCondition) = 0;
- virtual HRESULT STDMETHODCALLTYPE AddAutomationEventHandler(EVENTID eventId, __RPC__in_opt IUIAutomationElement *element, enum TreeScope scope, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__in_opt IUIAutomationEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveAutomationEventHandler(EVENTID eventId, __RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE AddPropertyChangedEventHandlerNativeArray(__RPC__in_opt IUIAutomationElement *element, enum TreeScope scope, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__in_opt IUIAutomationPropertyChangedEventHandler *handler, __RPC__in_ecount_full(propertyCount) PROPERTYID *propertyArray, int propertyCount) = 0;
- virtual HRESULT STDMETHODCALLTYPE AddPropertyChangedEventHandler(__RPC__in_opt IUIAutomationElement *element, enum TreeScope scope, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__in_opt IUIAutomationPropertyChangedEventHandler *handler, __RPC__in SAFEARRAY * propertyArray) = 0;
- virtual HRESULT STDMETHODCALLTYPE RemovePropertyChangedEventHandler(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationPropertyChangedEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE AddStructureChangedEventHandler(__RPC__in_opt IUIAutomationElement *element, enum TreeScope scope, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__in_opt IUIAutomationStructureChangedEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveStructureChangedEventHandler(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationStructureChangedEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE AddFocusChangedEventHandler(__RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__in_opt IUIAutomationFocusChangedEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveFocusChangedEventHandler(__RPC__in_opt IUIAutomationFocusChangedEventHandler *handler) = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveAllEventHandlers() = 0;
- virtual HRESULT STDMETHODCALLTYPE IntNativeArrayToSafeArray(__RPC__in_ecount_full(arrayCount) int *array, int arrayCount, __RPC__deref_out_opt SAFEARRAY **safeArray) = 0;
- virtual HRESULT STDMETHODCALLTYPE IntSafeArrayToNativeArray(__RPC__in SAFEARRAY * intArray, __RPC__deref_out_ecount_full_opt(*arrayCount) int **array, __RPC__out int *arrayCount) = 0;
- virtual HRESULT STDMETHODCALLTYPE RectToVariant(RECT rc, __RPC__out VARIANT *var) = 0;
- virtual HRESULT STDMETHODCALLTYPE VariantToRect(VARIANT var, __RPC__out RECT *rc) = 0;
- virtual HRESULT STDMETHODCALLTYPE SafeArrayToRectNativeArray(__RPC__in SAFEARRAY * rects, __RPC__deref_out_ecount_full_opt(*rectArrayCount) RECT **rectArray, __RPC__out int *rectArrayCount) = 0;
- virtual HRESULT STDMETHODCALLTYPE CreateProxyFactoryEntry(__RPC__in_opt IUIAutomationProxyFactory *factory, __RPC__deref_out_opt IUIAutomationProxyFactoryEntry **factoryEntry) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ProxyFactoryMapping(__RPC__deref_out_opt IUIAutomationProxyFactoryMapping **factoryMapping) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPropertyProgrammaticName(PROPERTYID property, __RPC__deref_out_opt BSTR *name) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPatternProgrammaticName(PATTERNID pattern, __RPC__deref_out_opt BSTR *name) = 0;
- virtual HRESULT STDMETHODCALLTYPE PollForPotentialSupportedPatterns(__RPC__in_opt IUIAutomationElement *pElement, __RPC__deref_out_opt SAFEARRAY **patternIds, __RPC__deref_out_opt SAFEARRAY **patternNames) = 0;
- virtual HRESULT STDMETHODCALLTYPE PollForPotentialSupportedProperties(__RPC__in_opt IUIAutomationElement *pElement, __RPC__deref_out_opt SAFEARRAY **propertyIds, __RPC__deref_out_opt SAFEARRAY **propertyNames) = 0;
- virtual HRESULT STDMETHODCALLTYPE CheckNotSupported(VARIANT value, __RPC__out BOOL *isNotSupported) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ReservedNotSupportedValue(__RPC__deref_out_opt IUnknown **notSupportedValue) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ReservedMixedAttributeValue(__RPC__deref_out_opt IUnknown **mixedAttributeValue) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromIAccessible(__RPC__in_opt IAccessible *accessible, int childId, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
- virtual HRESULT STDMETHODCALLTYPE ElementFromIAccessibleBuildCache(__RPC__in_opt IAccessible *accessible, int childId, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **element) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IUIAutomation, 0x30cbe57d, 0xd9d0, 0x452a, 0xab,0x13, 0x7a,0xc5,0xac,0x48,0x25,0xee)
-#endif
-#endif
-
-
-#ifndef __IUIAutomationTreeWalker_INTERFACE_DEFINED__
-#define __IUIAutomationTreeWalker_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IUIAutomationTreeWalker, 0x4042c624, 0x389c, 0x4afc, 0xa6,0x30, 0x9d,0xf8,0x54,0xa5,0x41,0xfc);
-MIDL_INTERFACE("4042c624-389c-4afc-a630-9df854a541fc")
-IUIAutomationTreeWalker : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetParentElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **parent) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetFirstChildElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **first) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetLastChildElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **last) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetNextSiblingElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **next) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPreviousSiblingElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **previous) = 0;
- virtual HRESULT STDMETHODCALLTYPE NormalizeElement(__RPC__in_opt IUIAutomationElement *element, __RPC__deref_out_opt IUIAutomationElement **normalized) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetParentElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **parent) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetFirstChildElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **first) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetLastChildElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **last) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetNextSiblingElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **next) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPreviousSiblingElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **previous) = 0;
- virtual HRESULT STDMETHODCALLTYPE NormalizeElementBuildCache(__RPC__in_opt IUIAutomationElement *element, __RPC__in_opt IUIAutomationCacheRequest *cacheRequest, __RPC__deref_out_opt IUIAutomationElement **normalized) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Condition(__RPC__deref_out_opt IUIAutomationCondition **condition) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IUIAutomationTreeWalker, 0x4042c624, 0x389c, 0x4afc, 0xa6,0x30, 0x9d,0xf8,0x54,0xa5,0x41,0xfc)
-#endif
-#endif
-
-DEFINE_GUID(CLSID_CUIAutomation, 0xff48dba4, 0x60ef, 0x4201, 0xaa,0x87, 0x54,0x10,0x3e,0xef,0x59,0x4e);
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiacontroltypeids_p.h b/src/gui/accessible/windows/apisupport/uiacontroltypeids_p.h
deleted file mode 100644
index 21d8080bc2..0000000000
--- a/src/gui/accessible/windows/apisupport/uiacontroltypeids_p.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIACONTROLTYPEIDS_H
-#define UIACONTROLTYPEIDS_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.
-//
-
-#define UIA_ButtonControlTypeId 50000
-#define UIA_CalendarControlTypeId 50001
-#define UIA_CheckBoxControlTypeId 50002
-#define UIA_ComboBoxControlTypeId 50003
-#define UIA_EditControlTypeId 50004
-#define UIA_HyperlinkControlTypeId 50005
-#define UIA_ImageControlTypeId 50006
-#define UIA_ListItemControlTypeId 50007
-#define UIA_ListControlTypeId 50008
-#define UIA_MenuControlTypeId 50009
-#define UIA_MenuBarControlTypeId 50010
-#define UIA_MenuItemControlTypeId 50011
-#define UIA_ProgressBarControlTypeId 50012
-#define UIA_RadioButtonControlTypeId 50013
-#define UIA_ScrollBarControlTypeId 50014
-#define UIA_SliderControlTypeId 50015
-#define UIA_SpinnerControlTypeId 50016
-#define UIA_StatusBarControlTypeId 50017
-#define UIA_TabControlTypeId 50018
-#define UIA_TabItemControlTypeId 50019
-#define UIA_TextControlTypeId 50020
-#define UIA_ToolBarControlTypeId 50021
-#define UIA_ToolTipControlTypeId 50022
-#define UIA_TreeControlTypeId 50023
-#define UIA_TreeItemControlTypeId 50024
-#define UIA_CustomControlTypeId 50025
-#define UIA_GroupControlTypeId 50026
-#define UIA_ThumbControlTypeId 50027
-#define UIA_DataGridControlTypeId 50028
-#define UIA_DataItemControlTypeId 50029
-#define UIA_DocumentControlTypeId 50030
-#define UIA_SplitButtonControlTypeId 50031
-#define UIA_WindowControlTypeId 50032
-#define UIA_PaneControlTypeId 50033
-#define UIA_HeaderControlTypeId 50034
-#define UIA_HeaderItemControlTypeId 50035
-#define UIA_TableControlTypeId 50036
-#define UIA_TitleBarControlTypeId 50037
-#define UIA_SeparatorControlTypeId 50038
-#define UIA_SemanticZoomControlTypeId 50039
-#define UIA_AppBarControlTypeId 50040
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiaerrorids_p.h b/src/gui/accessible/windows/apisupport/uiaerrorids_p.h
deleted file mode 100644
index b965fe5c30..0000000000
--- a/src/gui/accessible/windows/apisupport/uiaerrorids_p.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAERRORIDS_H
-#define UIAERRORIDS_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.
-//
-
-#define UIA_E_ELEMENTNOTENABLED 0x80040200
-#define UIA_E_ELEMENTNOTAVAILABLE 0x80040201
-#define UIA_E_NOCLICKABLEPOINT 0x80040202
-#define UIA_E_PROXYASSEMBLYNOTLOADED 0x80040203
-#define UIA_E_NOTSUPPORTED 0x80040204
-#define UIA_E_INVALIDOPERATION 0x80131509
-#define UIA_E_TIMEOUT 0x80131505
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiaeventids_p.h b/src/gui/accessible/windows/apisupport/uiaeventids_p.h
deleted file mode 100644
index 7ac6d85ec5..0000000000
--- a/src/gui/accessible/windows/apisupport/uiaeventids_p.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAEVENTIDS_H
-#define UIAEVENTIDS_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.
-//
-
-#define UIA_ToolTipOpenedEventId 20000
-#define UIA_ToolTipClosedEventId 20001
-#define UIA_StructureChangedEventId 20002
-#define UIA_MenuOpenedEventId 20003
-#define UIA_AutomationPropertyChangedEventId 20004
-#define UIA_AutomationFocusChangedEventId 20005
-#define UIA_AsyncContentLoadedEventId 20006
-#define UIA_MenuClosedEventId 20007
-#define UIA_LayoutInvalidatedEventId 20008
-#define UIA_Invoke_InvokedEventId 20009
-#define UIA_SelectionItem_ElementAddedToSelectionEventId 20010
-#define UIA_SelectionItem_ElementRemovedFromSelectionEventId 20011
-#define UIA_SelectionItem_ElementSelectedEventId 20012
-#define UIA_Selection_InvalidatedEventId 20013
-#define UIA_Text_TextSelectionChangedEventId 20014
-#define UIA_Text_TextChangedEventId 20015
-#define UIA_Window_WindowOpenedEventId 20016
-#define UIA_Window_WindowClosedEventId 20017
-#define UIA_MenuModeStartEventId 20018
-#define UIA_MenuModeEndEventId 20019
-#define UIA_InputReachedTargetEventId 20020
-#define UIA_InputReachedOtherElementEventId 20021
-#define UIA_InputDiscardedEventId 20022
-#define UIA_SystemAlertEventId 20023
-#define UIA_LiveRegionChangedEventId 20024
-#define UIA_HostedFragmentRootsInvalidatedEventId 20025
-#define UIA_Drag_DragStartEventId 20026
-#define UIA_Drag_DragCancelEventId 20027
-#define UIA_Drag_DragCompleteEventId 20028
-#define UIA_DropTarget_DragEnterEventId 20029
-#define UIA_DropTarget_DragLeaveEventId 20030
-#define UIA_DropTarget_DroppedEventId 20031
-#define UIA_TextEdit_TextChangedEventId 20032
-#define UIA_TextEdit_ConversionTargetChangedEventId 20033
-#define UIA_ChangesEventId 20034
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiageneralids_p.h b/src/gui/accessible/windows/apisupport/uiageneralids_p.h
deleted file mode 100644
index a6fdeceee3..0000000000
--- a/src/gui/accessible/windows/apisupport/uiageneralids_p.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAGENERALIDS_H
-#define UIAGENERALIDS_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.
-//
-
-#define UiaAppendRuntimeId 3
-#define UiaRootObjectId -25
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiapatternids_p.h b/src/gui/accessible/windows/apisupport/uiapatternids_p.h
deleted file mode 100644
index 0ff463cd36..0000000000
--- a/src/gui/accessible/windows/apisupport/uiapatternids_p.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAPATTERNIDS_H
-#define UIAPATTERNIDS_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.
-//
-
-#define UIA_InvokePatternId 10000
-#define UIA_SelectionPatternId 10001
-#define UIA_ValuePatternId 10002
-#define UIA_RangeValuePatternId 10003
-#define UIA_ScrollPatternId 10004
-#define UIA_ExpandCollapsePatternId 10005
-#define UIA_GridPatternId 10006
-#define UIA_GridItemPatternId 10007
-#define UIA_MultipleViewPatternId 10008
-#define UIA_WindowPatternId 10009
-#define UIA_SelectionItemPatternId 10010
-#define UIA_DockPatternId 10011
-#define UIA_TablePatternId 10012
-#define UIA_TableItemPatternId 10013
-#define UIA_TextPatternId 10014
-#define UIA_TogglePatternId 10015
-#define UIA_TransformPatternId 10016
-#define UIA_ScrollItemPatternId 10017
-#define UIA_LegacyIAccessiblePatternId 10018
-#define UIA_ItemContainerPatternId 10019
-#define UIA_VirtualizedItemPatternId 10020
-#define UIA_SynchronizedInputPatternId 10021
-#define UIA_ObjectModelPatternId 10022
-#define UIA_AnnotationPatternId 10023
-#define UIA_TextPattern2Id 10024
-#define UIA_StylesPatternId 10025
-#define UIA_SpreadsheetPatternId 10026
-#define UIA_SpreadsheetItemPatternId 10027
-#define UIA_TransformPattern2Id 10028
-#define UIA_TextChildPatternId 10029
-#define UIA_DragPatternId 10030
-#define UIA_DropTargetPatternId 10031
-#define UIA_TextEditPatternId 10032
-#define UIA_CustomNavigationPatternId 10033
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiapropertyids_p.h b/src/gui/accessible/windows/apisupport/uiapropertyids_p.h
deleted file mode 100644
index 77fc454e0f..0000000000
--- a/src/gui/accessible/windows/apisupport/uiapropertyids_p.h
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIAPROPERTYIDS_H
-#define UIAPROPERTYIDS_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.
-//
-
-#define UIA_RuntimeIdPropertyId 30000
-#define UIA_BoundingRectanglePropertyId 30001
-#define UIA_ProcessIdPropertyId 30002
-#define UIA_ControlTypePropertyId 30003
-#define UIA_LocalizedControlTypePropertyId 30004
-#define UIA_NamePropertyId 30005
-#define UIA_AcceleratorKeyPropertyId 30006
-#define UIA_AccessKeyPropertyId 30007
-#define UIA_HasKeyboardFocusPropertyId 30008
-#define UIA_IsKeyboardFocusablePropertyId 30009
-#define UIA_IsEnabledPropertyId 30010
-#define UIA_AutomationIdPropertyId 30011
-#define UIA_ClassNamePropertyId 30012
-#define UIA_HelpTextPropertyId 30013
-#define UIA_ClickablePointPropertyId 30014
-#define UIA_CulturePropertyId 30015
-#define UIA_IsControlElementPropertyId 30016
-#define UIA_IsContentElementPropertyId 30017
-#define UIA_LabeledByPropertyId 30018
-#define UIA_IsPasswordPropertyId 30019
-#define UIA_NativeWindowHandlePropertyId 30020
-#define UIA_ItemTypePropertyId 30021
-#define UIA_IsOffscreenPropertyId 30022
-#define UIA_OrientationPropertyId 30023
-#define UIA_FrameworkIdPropertyId 30024
-#define UIA_IsRequiredForFormPropertyId 30025
-#define UIA_ItemStatusPropertyId 30026
-#define UIA_IsDockPatternAvailablePropertyId 30027
-#define UIA_IsExpandCollapsePatternAvailablePropertyId 30028
-#define UIA_IsGridItemPatternAvailablePropertyId 30029
-#define UIA_IsGridPatternAvailablePropertyId 30030
-#define UIA_IsInvokePatternAvailablePropertyId 30031
-#define UIA_IsMultipleViewPatternAvailablePropertyId 30032
-#define UIA_IsRangeValuePatternAvailablePropertyId 30033
-#define UIA_IsScrollPatternAvailablePropertyId 30034
-#define UIA_IsScrollItemPatternAvailablePropertyId 30035
-#define UIA_IsSelectionItemPatternAvailablePropertyId 30036
-#define UIA_IsSelectionPatternAvailablePropertyId 30037
-#define UIA_IsTablePatternAvailablePropertyId 30038
-#define UIA_IsTableItemPatternAvailablePropertyId 30039
-#define UIA_IsTextPatternAvailablePropertyId 30040
-#define UIA_IsTogglePatternAvailablePropertyId 30041
-#define UIA_IsTransformPatternAvailablePropertyId 30042
-#define UIA_IsValuePatternAvailablePropertyId 30043
-#define UIA_IsWindowPatternAvailablePropertyId 30044
-#define UIA_ValueValuePropertyId 30045
-#define UIA_ValueIsReadOnlyPropertyId 30046
-#define UIA_RangeValueValuePropertyId 30047
-#define UIA_RangeValueIsReadOnlyPropertyId 30048
-#define UIA_RangeValueMinimumPropertyId 30049
-#define UIA_RangeValueMaximumPropertyId 30050
-#define UIA_RangeValueLargeChangePropertyId 30051
-#define UIA_RangeValueSmallChangePropertyId 30052
-#define UIA_ScrollHorizontalScrollPercentPropertyId 30053
-#define UIA_ScrollHorizontalViewSizePropertyId 30054
-#define UIA_ScrollVerticalScrollPercentPropertyId 30055
-#define UIA_ScrollVerticalViewSizePropertyId 30056
-#define UIA_ScrollHorizontallyScrollablePropertyId 30057
-#define UIA_ScrollVerticallyScrollablePropertyId 30058
-#define UIA_SelectionSelectionPropertyId 30059
-#define UIA_SelectionCanSelectMultiplePropertyId 30060
-#define UIA_SelectionIsSelectionRequiredPropertyId 30061
-#define UIA_GridRowCountPropertyId 30062
-#define UIA_GridColumnCountPropertyId 30063
-#define UIA_GridItemRowPropertyId 30064
-#define UIA_GridItemColumnPropertyId 30065
-#define UIA_GridItemRowSpanPropertyId 30066
-#define UIA_GridItemColumnSpanPropertyId 30067
-#define UIA_GridItemContainingGridPropertyId 30068
-#define UIA_DockDockPositionPropertyId 30069
-#define UIA_ExpandCollapseExpandCollapseStatePropertyId 30070
-#define UIA_MultipleViewCurrentViewPropertyId 30071
-#define UIA_MultipleViewSupportedViewsPropertyId 30072
-#define UIA_WindowCanMaximizePropertyId 30073
-#define UIA_WindowCanMinimizePropertyId 30074
-#define UIA_WindowWindowVisualStatePropertyId 30075
-#define UIA_WindowWindowInteractionStatePropertyId 30076
-#define UIA_WindowIsModalPropertyId 30077
-#define UIA_WindowIsTopmostPropertyId 30078
-#define UIA_SelectionItemIsSelectedPropertyId 30079
-#define UIA_SelectionItemSelectionContainerPropertyId 30080
-#define UIA_TableRowHeadersPropertyId 30081
-#define UIA_TableColumnHeadersPropertyId 30082
-#define UIA_TableRowOrColumnMajorPropertyId 30083
-#define UIA_TableItemRowHeaderItemsPropertyId 30084
-#define UIA_TableItemColumnHeaderItemsPropertyId 30085
-#define UIA_ToggleToggleStatePropertyId 30086
-#define UIA_TransformCanMovePropertyId 30087
-#define UIA_TransformCanResizePropertyId 30088
-#define UIA_TransformCanRotatePropertyId 30089
-#define UIA_IsLegacyIAccessiblePatternAvailablePropertyId 30090
-#define UIA_LegacyIAccessibleChildIdPropertyId 30091
-#define UIA_LegacyIAccessibleNamePropertyId 30092
-#define UIA_LegacyIAccessibleValuePropertyId 30093
-#define UIA_LegacyIAccessibleDescriptionPropertyId 30094
-#define UIA_LegacyIAccessibleRolePropertyId 30095
-#define UIA_LegacyIAccessibleStatePropertyId 30096
-#define UIA_LegacyIAccessibleHelpPropertyId 30097
-#define UIA_LegacyIAccessibleKeyboardShortcutPropertyId 30098
-#define UIA_LegacyIAccessibleSelectionPropertyId 30099
-#define UIA_LegacyIAccessibleDefaultActionPropertyId 30100
-#define UIA_AriaRolePropertyId 30101
-#define UIA_AriaPropertiesPropertyId 30102
-#define UIA_IsDataValidForFormPropertyId 30103
-#define UIA_ControllerForPropertyId 30104
-#define UIA_DescribedByPropertyId 30105
-#define UIA_FlowsToPropertyId 30106
-#define UIA_ProviderDescriptionPropertyId 30107
-#define UIA_IsItemContainerPatternAvailablePropertyId 30108
-#define UIA_IsVirtualizedItemPatternAvailablePropertyId 30109
-#define UIA_IsSynchronizedInputPatternAvailablePropertyId 30110
-#define UIA_OptimizeForVisualContentPropertyId 30111
-#define UIA_IsObjectModelPatternAvailablePropertyId 30112
-#define UIA_AnnotationAnnotationTypeIdPropertyId 30113
-#define UIA_AnnotationAnnotationTypeNamePropertyId 30114
-#define UIA_AnnotationAuthorPropertyId 30115
-#define UIA_AnnotationDateTimePropertyId 30116
-#define UIA_AnnotationTargetPropertyId 30117
-#define UIA_IsAnnotationPatternAvailablePropertyId 30118
-#define UIA_IsTextPattern2AvailablePropertyId 30119
-#define UIA_StylesStyleIdPropertyId 30120
-#define UIA_StylesStyleNamePropertyId 30121
-#define UIA_StylesFillColorPropertyId 30122
-#define UIA_StylesFillPatternStylePropertyId 30123
-#define UIA_StylesShapePropertyId 30124
-#define UIA_StylesFillPatternColorPropertyId 30125
-#define UIA_StylesExtendedPropertiesPropertyId 30126
-#define UIA_IsStylesPatternAvailablePropertyId 30127
-#define UIA_IsSpreadsheetPatternAvailablePropertyId 30128
-#define UIA_SpreadsheetItemFormulaPropertyId 30129
-#define UIA_SpreadsheetItemAnnotationObjectsPropertyId 30130
-#define UIA_SpreadsheetItemAnnotationTypesPropertyId 30131
-#define UIA_IsSpreadsheetItemPatternAvailablePropertyId 30132
-#define UIA_Transform2CanZoomPropertyId 30133
-#define UIA_IsTransformPattern2AvailablePropertyId 30134
-#define UIA_LiveSettingPropertyId 30135
-#define UIA_IsTextChildPatternAvailablePropertyId 30136
-#define UIA_IsDragPatternAvailablePropertyId 30137
-#define UIA_DragIsGrabbedPropertyId 30138
-#define UIA_DragDropEffectPropertyId 30139
-#define UIA_DragDropEffectsPropertyId 30140
-#define UIA_IsDropTargetPatternAvailablePropertyId 30141
-#define UIA_DropTargetDropTargetEffectPropertyId 30142
-#define UIA_DropTargetDropTargetEffectsPropertyId 30143
-#define UIA_DragGrabbedItemsPropertyId 30144
-#define UIA_Transform2ZoomLevelPropertyId 30145
-#define UIA_Transform2ZoomMinimumPropertyId 30146
-#define UIA_Transform2ZoomMaximumPropertyId 30147
-#define UIA_FlowsFromPropertyId 30148
-#define UIA_IsTextEditPatternAvailablePropertyId 30149
-#define UIA_IsPeripheralPropertyId 30150
-#define UIA_IsCustomNavigationPatternAvailablePropertyId 30151
-#define UIA_PositionInSetPropertyId 30152
-#define UIA_SizeOfSetPropertyId 30153
-#define UIA_LevelPropertyId 30154
-#define UIA_AnnotationTypesPropertyId 30155
-#define UIA_AnnotationObjectsPropertyId 30156
-#define UIA_LandmarkTypePropertyId 30157
-#define UIA_LocalizedLandmarkTypePropertyId 30158
-#define UIA_FullDescriptionPropertyId 30159
-#define UIA_FillColorPropertyId 30160
-#define UIA_OutlineColorPropertyId 30161
-#define UIA_FillTypePropertyId 30162
-#define UIA_VisualEffectsPropertyId 30163
-#define UIA_OutlineThicknessPropertyId 30164
-#define UIA_CenterPointPropertyId 30165
-#define UIA_RotationPropertyId 30166
-#define UIA_SizePropertyId 30167
-#define UIA_IsDialogPropertyId 30174
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiaserverinterfaces_p.h b/src/gui/accessible/windows/apisupport/uiaserverinterfaces_p.h
deleted file mode 100644
index 6cf15cacb0..0000000000
--- a/src/gui/accessible/windows/apisupport/uiaserverinterfaces_p.h
+++ /dev/null
@@ -1,367 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIASERVERINTERFACES_H
-#define UIASERVERINTERFACES_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 <unknwn.h>
-
-#ifndef __IRawElementProviderSimple_INTERFACE_DEFINED__
-#define __IRawElementProviderSimple_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IRawElementProviderSimple, 0xd6dd68d1, 0x86fd, 0x4332, 0x86,0x66, 0x9a,0xbe,0xde,0xa2,0xd2,0x4c);
-MIDL_INTERFACE("d6dd68d1-86fd-4332-8666-9abedea2d24c")
-IRawElementProviderSimple : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE get_ProviderOptions(__RPC__out enum ProviderOptions *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPatternProvider(PATTERNID patternId, __RPC__deref_out_opt IUnknown **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(PROPERTYID propertyId, __RPC__out VARIANT *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_HostRawElementProvider(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IRawElementProviderSimple, 0xd6dd68d1, 0x86fd, 0x4332, 0x86,0x66, 0x9a,0xbe,0xde,0xa2,0xd2,0x4c)
-#endif
-#endif
-
-
-#ifndef __IRawElementProviderFragmentRoot_FWD_DEFINED__
-#define __IRawElementProviderFragmentRoot_FWD_DEFINED__
-typedef interface IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot;
-#endif
-
-
-#ifndef __IRawElementProviderFragment_FWD_DEFINED__
-#define __IRawElementProviderFragment_FWD_DEFINED__
-typedef interface IRawElementProviderFragment IRawElementProviderFragment;
-#endif
-
-
-#ifndef __IRawElementProviderFragment_INTERFACE_DEFINED__
-#define __IRawElementProviderFragment_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IRawElementProviderFragment, 0xf7063da8, 0x8359, 0x439c, 0x92,0x97, 0xbb,0xc5,0x29,0x9a,0x7d,0x87);
-MIDL_INTERFACE("f7063da8-8359-439c-9297-bbc5299a7d87")
-IRawElementProviderFragment : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Navigate(enum NavigateDirection direction, __RPC__deref_out_opt IRawElementProviderFragment **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetRuntimeId(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_BoundingRectangle(__RPC__out struct UiaRect *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetEmbeddedFragmentRoots(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE SetFocus() = 0;
- virtual HRESULT STDMETHODCALLTYPE get_FragmentRoot(__RPC__deref_out_opt IRawElementProviderFragmentRoot **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IRawElementProviderFragment, 0xf7063da8, 0x8359, 0x439c, 0x92,0x97, 0xbb,0xc5,0x29,0x9a,0x7d,0x87)
-#endif
-#endif
-
-
-#ifndef __IRawElementProviderFragmentRoot_INTERFACE_DEFINED__
-#define __IRawElementProviderFragmentRoot_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IRawElementProviderFragmentRoot, 0x620ce2a5, 0xab8f, 0x40a9, 0x86,0xcb, 0xde,0x3c,0x75,0x59,0x9b,0x58);
-MIDL_INTERFACE("620ce2a5-ab8f-40a9-86cb-de3c75599b58")
-IRawElementProviderFragmentRoot : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE ElementProviderFromPoint(double x, double y, __RPC__deref_out_opt IRawElementProviderFragment **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetFocus(__RPC__deref_out_opt IRawElementProviderFragment **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IRawElementProviderFragmentRoot, 0x620ce2a5, 0xab8f, 0x40a9, 0x86,0xcb, 0xde,0x3c,0x75,0x59,0x9b,0x58)
-#endif
-#endif
-
-
-#ifndef __IValueProvider_INTERFACE_DEFINED__
-#define __IValueProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IValueProvider, 0xc7935180, 0x6fb3, 0x4201, 0xb1,0x74, 0x7d,0xf7,0x3a,0xdb,0xf6,0x4a);
-MIDL_INTERFACE("c7935180-6fb3-4201-b174-7df73adbf64a")
-IValueProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE SetValue(__RPC__in LPCWSTR val) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Value(__RPC__deref_out_opt BSTR *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsReadOnly(__RPC__out BOOL *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IValueProvider, 0xc7935180, 0x6fb3, 0x4201, 0xb1,0x74, 0x7d,0xf7,0x3a,0xdb,0xf6,0x4a)
-#endif
-#endif
-
-
-#ifndef __IRangeValueProvider_INTERFACE_DEFINED__
-#define __IRangeValueProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IRangeValueProvider, 0x36dc7aef, 0x33e6, 0x4691, 0xaf,0xe1, 0x2b,0xe7,0x27,0x4b,0x3d,0x33);
-MIDL_INTERFACE("36dc7aef-33e6-4691-afe1-2be7274b3d33")
-IRangeValueProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE SetValue(double val) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Value(__RPC__out double *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsReadOnly(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Maximum(__RPC__out double *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Minimum(__RPC__out double *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_LargeChange(__RPC__out double *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_SmallChange(__RPC__out double *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IRangeValueProvider, 0x36dc7aef, 0x33e6, 0x4691, 0xaf,0xe1, 0x2b,0xe7,0x27,0x4b,0x3d,0x33)
-#endif
-#endif
-
-
-#ifndef __ITextRangeProvider_INTERFACE_DEFINED__
-#define __ITextRangeProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ITextRangeProvider, 0x5347ad7b, 0xc355, 0x46f8, 0xaf,0xf5, 0x90,0x90,0x33,0x58,0x2f,0x63);
-MIDL_INTERFACE("5347ad7b-c355-46f8-aff5-909033582f63")
-ITextRangeProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Clone(__RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE Compare(__RPC__in_opt ITextRangeProvider *range, __RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE CompareEndpoints(enum TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider *targetRange, enum TextPatternRangeEndpoint targetEndpoint, __RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE ExpandToEnclosingUnit(enum TextUnit unit) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindAttribute(TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE FindText(__RPC__in BSTR text, BOOL backward, BOOL ignoreCase, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetAttributeValue(TEXTATTRIBUTEID attributeId, __RPC__out VARIANT *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetBoundingRectangles(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetEnclosingElement(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetText(int maxLength, __RPC__deref_out_opt BSTR *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE Move(enum TextUnit unit, int count, __RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE MoveEndpointByUnit(enum TextPatternRangeEndpoint endpoint, enum TextUnit unit, int count, __RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE MoveEndpointByRange(enum TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider *targetRange, enum TextPatternRangeEndpoint targetEndpoint) = 0;
- virtual HRESULT STDMETHODCALLTYPE Select() = 0;
- virtual HRESULT STDMETHODCALLTYPE AddToSelection() = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection() = 0;
- virtual HRESULT STDMETHODCALLTYPE ScrollIntoView(BOOL alignToTop) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetChildren(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ITextRangeProvider, 0x5347ad7b, 0xc355, 0x46f8, 0xaf,0xf5, 0x90,0x90,0x33,0x58,0x2f,0x63)
-#endif
-#endif
-
-
-#ifndef __ITextProvider_INTERFACE_DEFINED__
-#define __ITextProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ITextProvider, 0x3589c92c, 0x63f3, 0x4367, 0x99,0xbb, 0xad,0xa6,0x53,0xb7,0x7c,0xf2);
-MIDL_INTERFACE("3589c92c-63f3-4367-99bb-ada653b77cf2")
-ITextProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetSelection(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetVisibleRanges(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE RangeFromChild(__RPC__in_opt IRawElementProviderSimple *childElement, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE RangeFromPoint(struct UiaPoint point, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_DocumentRange(__RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_SupportedTextSelection(__RPC__out enum SupportedTextSelection *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ITextProvider, 0x3589c92c, 0x63f3, 0x4367, 0x99,0xbb, 0xad,0xa6,0x53,0xb7,0x7c,0xf2)
-#endif
-#endif
-
-
-#ifndef __ITextProvider2_INTERFACE_DEFINED__
-#define __ITextProvider2_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ITextProvider2, 0x0dc5e6ed, 0x3e16, 0x4bf1, 0x8f,0x9a, 0xa9,0x79,0x87,0x8b,0xc1,0x95);
-MIDL_INTERFACE("0dc5e6ed-3e16-4bf1-8f9a-a979878bc195")
-ITextProvider2 : public ITextProvider
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE RangeFromAnnotation(__RPC__in_opt IRawElementProviderSimple *annotationElement, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetCaretRange(__RPC__out BOOL *isActive, __RPC__deref_out_opt ITextRangeProvider **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ITextProvider2, 0x0dc5e6ed, 0x3e16, 0x4bf1, 0x8f,0x9a, 0xa9,0x79,0x87,0x8b,0xc1,0x95)
-#endif
-#endif
-
-
-#ifndef __IToggleProvider_INTERFACE_DEFINED__
-#define __IToggleProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IToggleProvider, 0x56d00bd0, 0xc4f4, 0x433c, 0xa8,0x36, 0x1a,0x52,0xa5,0x7e,0x08,0x92);
-MIDL_INTERFACE("56d00bd0-c4f4-433c-a836-1a52a57e0892")
-IToggleProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Toggle() = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ToggleState(__RPC__out enum ToggleState *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IToggleProvider, 0x56d00bd0, 0xc4f4, 0x433c, 0xa8,0x36, 0x1a,0x52,0xa5,0x7e,0x08,0x92)
-#endif
-#endif
-
-
-#ifndef __IInvokeProvider_INTERFACE_DEFINED__
-#define __IInvokeProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IInvokeProvider, 0x54fcb24b, 0xe18e, 0x47a2, 0xb4,0xd3, 0xec,0xcb,0xe7,0x75,0x99,0xa2);
-MIDL_INTERFACE("54fcb24b-e18e-47a2-b4d3-eccbe77599a2")
-IInvokeProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Invoke() = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IInvokeProvider, 0x54fcb24b, 0xe18e, 0x47a2, 0xb4,0xd3, 0xec,0xcb,0xe7,0x75,0x99,0xa2)
-#endif
-#endif
-
-
-#ifndef __ISelectionProvider_INTERFACE_DEFINED__
-#define __ISelectionProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ISelectionProvider, 0xfb8b03af, 0x3bdf, 0x48d4, 0xbd,0x36, 0x1a,0x65,0x79,0x3b,0xe1,0x68);
-MIDL_INTERFACE("fb8b03af-3bdf-48d4-bd36-1a65793be168")
-ISelectionProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetSelection(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(__RPC__out BOOL *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ISelectionProvider, 0xfb8b03af, 0x3bdf, 0x48d4, 0xbd,0x36, 0x1a,0x65,0x79,0x3b,0xe1,0x68)
-#endif
-#endif
-
-
-#ifndef __ISelectionItemProvider_INTERFACE_DEFINED__
-#define __ISelectionItemProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ISelectionItemProvider, 0x2acad808, 0xb2d4, 0x452d, 0xa4,0x07, 0x91,0xff,0x1a,0xd1,0x67,0xb2);
-MIDL_INTERFACE("2acad808-b2d4-452d-a407-91ff1ad167b2")
-ISelectionItemProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Select() = 0;
- virtual HRESULT STDMETHODCALLTYPE AddToSelection() = 0;
- virtual HRESULT STDMETHODCALLTYPE RemoveFromSelection() = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsSelected(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_SelectionContainer(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ISelectionItemProvider, 0x2acad808, 0xb2d4, 0x452d, 0xa4,0x07, 0x91,0xff,0x1a,0xd1,0x67,0xb2)
-#endif
-#endif
-
-
-#ifndef __ITableProvider_INTERFACE_DEFINED__
-#define __ITableProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ITableProvider, 0x9c860395, 0x97b3, 0x490a, 0xb5,0x2a, 0x85,0x8c,0xc2,0x2a,0xf1,0x66);
-MIDL_INTERFACE("9c860395-97b3-490a-b52a-858cc22af166")
-ITableProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetRowHeaders(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetColumnHeaders(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_RowOrColumnMajor(__RPC__out enum RowOrColumnMajor *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ITableProvider, 0x9c860395, 0x97b3, 0x490a, 0xb5,0x2a, 0x85,0x8c,0xc2,0x2a,0xf1,0x66)
-#endif
-#endif
-
-
-#ifndef __ITableItemProvider_INTERFACE_DEFINED__
-#define __ITableItemProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_ITableItemProvider, 0xb9734fa6, 0x771f, 0x4d78, 0x9c,0x90, 0x25,0x17,0x99,0x93,0x49,0xcd);
-MIDL_INTERFACE("b9734fa6-771f-4d78-9c90-2517999349cd")
-ITableItemProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetRowHeaderItems(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE GetColumnHeaderItems(__RPC__deref_out_opt SAFEARRAY **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(ITableItemProvider, 0xb9734fa6, 0x771f, 0x4d78, 0x9c,0x90, 0x25,0x17,0x99,0x93,0x49,0xcd)
-#endif
-#endif
-
-
-#ifndef __IGridProvider_INTERFACE_DEFINED__
-#define __IGridProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IGridProvider, 0xb17d6187, 0x0907, 0x464b, 0xa1,0x68, 0x0e,0xf1,0x7a,0x15,0x72,0xb1);
-MIDL_INTERFACE("b17d6187-0907-464b-a168-0ef17a1572b1")
-IGridProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE GetItem(int row, int column, __RPC__deref_out_opt IRawElementProviderSimple **pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_RowCount(__RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ColumnCount(__RPC__out int *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IGridProvider, 0xb17d6187, 0x0907, 0x464b, 0xa1,0x68, 0x0e,0xf1,0x7a,0x15,0x72,0xb1)
-#endif
-#endif
-
-
-#ifndef __IGridItemProvider_INTERFACE_DEFINED__
-#define __IGridItemProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IGridItemProvider, 0xd02541f1, 0xfb81, 0x4d64, 0xae,0x32, 0xf5,0x20,0xf8,0xa6,0xdb,0xd1);
-MIDL_INTERFACE("d02541f1-fb81-4d64-ae32-f520f8a6dbd1")
-IGridItemProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE get_Row(__RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_Column(__RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_RowSpan(__RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ColumnSpan(__RPC__out int *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ContainingGrid(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IGridItemProvider, 0xd02541f1, 0xfb81, 0x4d64, 0xae,0x32, 0xf5,0x20,0xf8,0xa6,0xdb,0xd1)
-#endif
-#endif
-
-
-#ifndef __IWindowProvider_INTERFACE_DEFINED__
-#define __IWindowProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IWindowProvider, 0x987df77b, 0xdb06, 0x4d77, 0x8f,0x8a, 0x86,0xa9,0xc3,0xbb,0x90,0xb9);
-MIDL_INTERFACE("987df77b-db06-4d77-8f8a-86a9c3bb90b9")
-IWindowProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE SetVisualState(enum WindowVisualState state) = 0;
- virtual HRESULT STDMETHODCALLTYPE Close( void) = 0;
- virtual HRESULT STDMETHODCALLTYPE WaitForInputIdle(int milliseconds, __RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CanMaximize(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_CanMinimize(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsModal(__RPC__out BOOL *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_WindowVisualState(__RPC__out enum WindowVisualState *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_WindowInteractionState(__RPC__out enum WindowInteractionState *pRetVal) = 0;
- virtual HRESULT STDMETHODCALLTYPE get_IsTopmost(__RPC__out BOOL *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IWindowProvider, 0x987df77b, 0xdb06, 0x4d77, 0x8f,0x8a, 0x86,0xa9,0xc3,0xbb,0x90,0xb9)
-#endif
-#endif
-
-
-#ifndef __IExpandCollapseProvider_INTERFACE_DEFINED__
-#define __IExpandCollapseProvider_INTERFACE_DEFINED__
-DEFINE_GUID(IID_IExpandCollapseProvider, 0xd847d3a5, 0xcab0, 0x4a98, 0x8c,0x32, 0xec,0xb4,0x5c,0x59,0xad,0x24);
-MIDL_INTERFACE("d847d3a5-cab0-4a98-8c32-ecb45c59ad24")
-IExpandCollapseProvider : public IUnknown
-{
-public:
- virtual HRESULT STDMETHODCALLTYPE Expand() = 0;
- virtual HRESULT STDMETHODCALLTYPE Collapse() = 0;
- virtual HRESULT STDMETHODCALLTYPE get_ExpandCollapseState(__RPC__out enum ExpandCollapseState *pRetVal) = 0;
-};
-#ifdef __CRT_UUID_DECL
-__CRT_UUID_DECL(IExpandCollapseProvider, 0xd847d3a5, 0xcab0, 0x4a98, 0x8c,0x32, 0xec,0xb4,0x5c,0x59,0xad,0x24)
-#endif
-#endif
-
-#endif
diff --git a/src/gui/accessible/windows/apisupport/uiatypes_p.h b/src/gui/accessible/windows/apisupport/uiatypes_p.h
deleted file mode 100644
index 465bd07a07..0000000000
--- a/src/gui/accessible/windows/apisupport/uiatypes_p.h
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef UIATYPES_H
-#define UIATYPES_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.
-//
-
-typedef int PROPERTYID;
-typedef int PATTERNID;
-typedef int EVENTID;
-typedef int TEXTATTRIBUTEID;
-typedef int CONTROLTYPEID;
-typedef int LANDMARKTYPEID;
-typedef int METADATAID;
-
-typedef void *UIA_HWND;
-
-enum NavigateDirection {
- NavigateDirection_Parent = 0,
- NavigateDirection_NextSibling = 1,
- NavigateDirection_PreviousSibling = 2,
- NavigateDirection_FirstChild = 3,
- NavigateDirection_LastChild = 4
-};
-
-enum ProviderOptions {
- ProviderOptions_ClientSideProvider = 0x1,
- ProviderOptions_ServerSideProvider = 0x2,
- ProviderOptions_NonClientAreaProvider = 0x4,
- ProviderOptions_OverrideProvider = 0x8,
- ProviderOptions_ProviderOwnsSetFocus = 0x10,
- ProviderOptions_UseComThreading = 0x20,
- ProviderOptions_RefuseNonClientSupport = 0x40,
- ProviderOptions_HasNativeIAccessible = 0x80,
- ProviderOptions_UseClientCoordinates = 0x100
-};
-
-enum SupportedTextSelection {
- SupportedTextSelection_None = 0,
- SupportedTextSelection_Single = 1,
- SupportedTextSelection_Multiple = 2
-};
-
-enum TextUnit {
- TextUnit_Character = 0,
- TextUnit_Format = 1,
- TextUnit_Word = 2,
- TextUnit_Line = 3,
- TextUnit_Paragraph = 4,
- TextUnit_Page = 5,
- TextUnit_Document = 6
-};
-
-enum TextPatternRangeEndpoint {
- TextPatternRangeEndpoint_Start = 0,
- TextPatternRangeEndpoint_End = 1
-};
-
-enum CaretPosition {
- CaretPosition_Unknown = 0,
- CaretPosition_EndOfLine = 1,
- CaretPosition_BeginningOfLine = 2
-};
-
-enum ToggleState {
- ToggleState_Off = 0,
- ToggleState_On = 1,
- ToggleState_Indeterminate = 2
-};
-
-enum RowOrColumnMajor {
- RowOrColumnMajor_RowMajor = 0,
- RowOrColumnMajor_ColumnMajor = 1,
- RowOrColumnMajor_Indeterminate = 2
-};
-
-enum TreeScope {
- TreeScope_None = 0,
- TreeScope_Element = 0x1,
- TreeScope_Children = 0x2,
- TreeScope_Descendants = 0x4,
- TreeScope_Parent = 0x8,
- TreeScope_Ancestors = 0x10,
- TreeScope_Subtree = TreeScope_Element | TreeScope_Children | TreeScope_Descendants
-};
-
-enum OrientationType {
- OrientationType_None = 0,
- OrientationType_Horizontal = 1,
- OrientationType_Vertical = 2
-};
-
-enum PropertyConditionFlags {
- PropertyConditionFlags_None = 0,
- PropertyConditionFlags_IgnoreCase = 1
-};
-
-enum WindowVisualState {
- WindowVisualState_Normal = 0,
- WindowVisualState_Maximized = 1,
- WindowVisualState_Minimized = 2
-};
-
-enum WindowInteractionState {
- WindowInteractionState_Running = 0,
- WindowInteractionState_Closing = 1,
- WindowInteractionState_ReadyForUserInteraction = 2,
- WindowInteractionState_BlockedByModalWindow = 3,
- WindowInteractionState_NotResponding = 4
-};
-
-enum ExpandCollapseState {
- ExpandCollapseState_Collapsed = 0,
- ExpandCollapseState_Expanded = 1,
- ExpandCollapseState_PartiallyExpanded = 2,
- ExpandCollapseState_LeafNode = 3
-};
-
-enum NotificationKind {
- NotificationKind_ItemAdded = 0,
- NotificationKind_ItemRemoved = 1,
- NotificationKind_ActionCompleted = 2,
- NotificationKind_ActionAborted = 3,
- NotificationKind_Other = 4
-};
-
-enum NotificationProcessing {
- NotificationProcessing_ImportantAll = 0,
- NotificationProcessing_ImportantMostRecent = 1,
- NotificationProcessing_All = 2,
- NotificationProcessing_MostRecent = 3,
- NotificationProcessing_CurrentThenMostRecent = 4
-};
-
-struct UiaRect {
- double left;
- double top;
- double width;
- double height;
-};
-
-struct UiaPoint {
- double x;
- double y;
-};
-
-#endif
diff --git a/src/gui/compat/removed_api.cpp b/src/gui/compat/removed_api.cpp
index c38b30d812..a642c33c42 100644
--- a/src/gui/compat/removed_api.cpp
+++ b/src/gui/compat/removed_api.cpp
@@ -7,8 +7,21 @@
QT_USE_NAMESPACE
+#if QT_GUI_REMOVED_SINCE(6, 4)
+
+#include "qpagesize.h" // removed duplicate declaration of op==
+ // (still caused an symbol on some platforms)
+
+// #include "qotherheader.h"
+// // implement removed functions from qotherheader.h
+// order sections alphabetically
+
+#endif // QT_GUI_REMOVED_SINCE(6, 4)
+
#if QT_GUI_REMOVED_SINCE(6, 6)
+#include "qpixmapcache.h" // inlined API
+
// #include "qotherheader.h"
// // implement removed functions from qotherheader.h
// order sections alphabetically
@@ -17,8 +30,41 @@ QT_USE_NAMESPACE
#if QT_GUI_REMOVED_SINCE(6, 7)
+#include "qtextdocument.h"
+
+bool Qt::mightBeRichText(const QString& text)
+{
+ return Qt::mightBeRichText(qToStringViewIgnoringNull(text));
+}
+
+#endif // QT_GUI_REMOVED_SINCE(6, 7)
+
+#if QT_GUI_REMOVED_SINCE(6, 8)
+
+#include "qpagelayout.h"
+
+bool QPageLayout::setMargins(const QMarginsF &margins)
+{
+ return setMargins(margins, OutOfBoundsPolicy::Reject);
+}
+
+bool QPageLayout::setLeftMargin(qreal leftMargin)
+{
+ return setLeftMargin(leftMargin, OutOfBoundsPolicy::Reject);
+}
+
+bool QPageLayout::setRightMargin(qreal rightMargin)
+{
+ return setRightMargin(rightMargin, OutOfBoundsPolicy::Reject);
+}
+
+bool QPageLayout::setTopMargin(qreal topMargin)
+{
+ return setTopMargin(topMargin, OutOfBoundsPolicy::Reject);
+}
+
// #include "qotherheader.h"
// // implement removed functions from qotherheader.h
// order sections alphabetically
-#endif // QT_GUI_REMOVED_SINCE(6, 7)
+#endif // QT_GUI_REMOVED_SINCE(6, 8)
diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake
index 02a80130d5..da08863ac6 100644
--- a/src/gui/configure.cmake
+++ b/src/gui/configure.cmake
@@ -62,6 +62,7 @@ qt_find_package(WrapVulkanHeaders PROVIDED_TARGETS WrapVulkanHeaders::WrapVulkan
MODULE_NAME gui QMAKE_LIB vulkan MARK_OPTIONAL)
if((LINUX) OR QT_FIND_ALL_PACKAGES_ALWAYS)
qt_find_package(Wayland PROVIDED_TARGETS Wayland::Server MODULE_NAME gui QMAKE_LIB wayland_server)
+ qt_find_package(Wayland PROVIDED_TARGETS Wayland::Client MODULE_NAME gui QMAKE_LIB wayland_client)
endif()
if((X11_SUPPORTED) OR QT_FIND_ALL_PACKAGES_ALWAYS)
qt_find_package(X11 PROVIDED_TARGETS X11::X11 MODULE_NAME gui QMAKE_LIB xlib)
@@ -142,6 +143,7 @@ if((X11_SUPPORTED) OR QT_FIND_ALL_PACKAGES_ALWAYS)
endif()
qt_add_qmake_lib_dependency(xrender xlib)
+qt_find_package(RenderDoc PROVIDED_TARGETS RenderDoc::RenderDoc)
#### Tests
@@ -232,6 +234,7 @@ EGLDeviceEXT device = 0;
EGLStreamKHR stream = 0;
EGLOutputLayerEXT layer = 0;
(void) EGL_DRM_CRTC_EXT;
+(void) EGL_DRM_MASTER_FD_EXT;
/* END TEST: */
return 0;
}
@@ -577,7 +580,7 @@ qt_config_compile_test(directwrite3
int main(int, char **)
{
IUnknown *factory = nullptr;
- DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3),
+ DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory6),
&factory);
return 0;
}
@@ -613,12 +616,26 @@ int main(int, char **)
}
")
+qt_config_compile_test(renderdoc
+ LIBRARIES
+ RenderDoc::RenderDoc
+ LABEL "RenderDoc header check"
+ CODE
+"#include <renderdoc_app.h>
+int main(int, char **)
+{
+ if (RENDERDOC_Version::eRENDERDOC_API_Version_1_6_0)
+ return 0;
+ return 0;
+}
+")
+
#### Features
qt_feature("accessibility-atspi-bridge" PUBLIC PRIVATE
LABEL "ATSPI Bridge"
- CONDITION QT_FEATURE_accessibility AND QT_FEATURE_xcb AND QT_FEATURE_dbus AND ATSPI2_FOUND
+ CONDITION QT_FEATURE_accessibility AND QT_FEATURE_dbus AND ATSPI2_FOUND
)
qt_feature_definition("accessibility-atspi-bridge" "QT_NO_ACCESSIBILITY_ATSPI_BRIDGE" NEGATE VALUE "1")
qt_feature("directfb" PRIVATE
@@ -794,6 +811,10 @@ qt_feature("vulkan" PUBLIC
LABEL "Vulkan"
CONDITION QT_FEATURE_library AND QT_FEATURE_vkgen AND WrapVulkanHeaders_FOUND
)
+qt_feature("metal" PUBLIC
+ LABEL "Metal"
+ CONDITION MACOS OR IOS OR VISIONOS
+)
qt_feature("vkkhrdisplay" PRIVATE
SECTION "Platform plugins"
LABEL "VK_KHR_display"
@@ -847,7 +868,7 @@ qt_feature("eglfs_rcar" PRIVATE
)
qt_feature("eglfs_viv_wl" PRIVATE
LABEL "EGLFS i.Mx6 Wayland"
- CONDITION QT_FEATURE_eglfs_viv AND Wayland_FOUND
+ CONDITION QT_FEATURE_eglfs_viv AND TARGET Wayland::Server
)
qt_feature("eglfs_openwfd" PRIVATE
LABEL "EGLFS OpenWFD"
@@ -872,7 +893,7 @@ qt_feature("jpeg" PRIVATE
CONDITION QT_FEATURE_imageformatplugin
DISABLE INPUT_libjpeg STREQUAL 'no'
)
-qt_feature_definition("jpeg" "QT_NO_IMAGEFORMAT_JPEG" NEGATE)
+qt_feature_definition("jpeg" "QT_NO_IMAGEFORMAT_JPEG" NEGATE VALUE "1")
qt_feature("system-jpeg" PRIVATE
LABEL " Using system libjpeg"
CONDITION QT_FEATURE_jpeg AND JPEG_FOUND
@@ -995,7 +1016,8 @@ qt_feature("system-textmarkdownreader" PUBLIC
qt_feature("textmarkdownwriter" PUBLIC
SECTION "Kernel"
LABEL "MarkdownWriter"
- PURPOSE "Provides a Markdown (CommonMark) writer"
+ CONDITION QT_FEATURE_regularexpression
+ PURPOSE "Provides a Markdown (CommonMark and GitHub) writer"
)
qt_feature("textodfwriter" PUBLIC
SECTION "Kernel"
@@ -1200,6 +1222,7 @@ qt_feature("raster-fp" PRIVATE
SECTION "Painting"
LABEL "QPainter - floating point raster"
PURPOSE "Internal painting support for floating point rasterization."
+ CONDITION NOT VXWORKS # QTBUG-115777
)
qt_feature("undocommand" PUBLIC
SECTION "Utilities"
@@ -1220,7 +1243,19 @@ qt_feature("undogroup" PUBLIC
PURPOSE "Provides the ability to cluster QUndoCommands."
CONDITION QT_FEATURE_undostack
)
+qt_feature("graphicsframecapture" PRIVATE
+ SECTION "Utilities"
+ LABEL "QGraphicsFrameCapture"
+ PURPOSE "Provides a way to capture 3D graphics API calls for a rendered frame."
+ CONDITION TEST_renderdoc OR (MACOS OR IOS)
+)
qt_feature_definition("undogroup" "QT_NO_UNDOGROUP" NEGATE VALUE "1")
+qt_feature("wayland" PUBLIC
+ SECTION "Platform plugins"
+ LABEL "Wayland"
+ CONDITION TARGET Wayland::Client
+)
+
qt_configure_add_summary_section(NAME "Qt Gui")
qt_configure_add_summary_entry(ARGS "accessibility")
qt_configure_add_summary_entry(ARGS "freetype")
@@ -1258,6 +1293,8 @@ qt_configure_add_summary_entry(ARGS "opengles31")
qt_configure_add_summary_entry(ARGS "opengles32")
qt_configure_end_summary_section() # end of "OpenGL" section
qt_configure_add_summary_entry(ARGS "vulkan")
+qt_configure_add_summary_entry(ARGS "metal")
+qt_configure_add_summary_entry(ARGS "graphicsframecapture")
qt_configure_add_summary_entry(ARGS "sessionmanager")
qt_configure_end_summary_section() # end of "Qt Gui" section
qt_configure_add_summary_section(NAME "Features used by QPA backends")
@@ -1335,7 +1372,7 @@ qt_configure_add_report_entry(
qt_configure_add_report_entry(
TYPE ERROR
MESSAGE "The OpenGL functionality tests failed! You might need to modify the OpenGL package search path by setting the OpenGL_DIR CMake variable to the OpenGL library's installation directory."
- CONDITION QT_FEATURE_gui AND NOT WATCHOS AND ( NOT INPUT_opengl STREQUAL 'no' ) AND NOT QT_FEATURE_opengl_desktop AND NOT QT_FEATURE_opengles2 AND NOT QT_FEATURE_opengl_dynamic
+ CONDITION QT_FEATURE_gui AND NOT WATCHOS AND NOT VISIONOS AND ( NOT INPUT_opengl STREQUAL 'no' ) AND NOT QT_FEATURE_opengl_desktop AND NOT QT_FEATURE_opengles2 AND NOT QT_FEATURE_opengl_dynamic
)
qt_configure_add_report_entry(
TYPE WARNING
diff --git a/src/gui/doc/images/qpainter-concentriccircles.png b/src/gui/doc/images/qpainter-concentriccircles.png
index 4889dcd76d..d9489a4162 100644
--- a/src/gui/doc/images/qpainter-concentriccircles.png
+++ b/src/gui/doc/images/qpainter-concentriccircles.png
Binary files differ
diff --git a/src/gui/doc/qtgui.qdocconf b/src/gui/doc/qtgui.qdocconf
index 3698cf34df..b94f11849c 100644
--- a/src/gui/doc/qtgui.qdocconf
+++ b/src/gui/doc/qtgui.qdocconf
@@ -63,6 +63,8 @@ imagedirs += images \
excludefiles += ../kernel/qtestsupport_gui.cpp \
../painting/qdrawhelper_ssse3.cpp
+manifestmeta.highlighted.names = "QtGui/Hello Vulkan Cubes Example"
+
navigation.landingpage = "Qt GUI"
navigation.cppclassespage = "Qt GUI C++ Classes"
@@ -73,5 +75,5 @@ spurious += "Undocumented enum item '.*' in QGradient::Preset"
macro.svgcolor.HTML = "<div style=\"padding:10px;color:#fff;background:\1;\"></div>"
macro.svgcolor.DocBook = "<db:phrase role=\"color:\1\">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</db:phrase>"
-# Fail the documentation build if there are more warnings than the limit
+# Enforce zero documentation warnings
warninglimit = 0
diff --git a/src/gui/doc/snippets/code/doc_src_richtext.qdoc b/src/gui/doc/snippets/code/doc_src_richtext.qdoc
index 39e29caa8d..0c69514210 100644
--- a/src/gui/doc/snippets/code/doc_src_richtext.qdoc
+++ b/src/gui/doc/snippets/code/doc_src_richtext.qdoc
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
//! [7]
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />
diff --git a/src/gui/doc/snippets/code/src_gui_image_qicon.cpp b/src/gui/doc/snippets/code/src_gui_image_qicon.cpp
index d446f16c3c..a7f27a7fdd 100644
--- a/src/gui/doc/snippets/code/src_gui_image_qicon.cpp
+++ b/src/gui/doc/snippets/code/src_gui_image_qicon.cpp
@@ -8,7 +8,7 @@ namespace src_gui_image_qicon {
struct MyWidget : public QWidget
{
- void drawIcon(QPainter *painter, QPoint pos);
+ void drawIcon(QPainter *painter, const QRect &rect);
bool isChecked() { return true; }
QIcon icon;
};
@@ -17,9 +17,13 @@ void wrapper0() {
//! [0]
QToolButton *button = new QToolButton;
-button->setIcon(QIcon("open.xpm"));
+button->setIcon(QIcon("open.png"));
//! [0]
+//! [addFile]
+QIcon openIcon("open.png");
+openIcon.addFile("open-disabled.png", QIcon::Disabled);
+//! [addFile]
//! [1]
button->setIcon(QIcon());
@@ -29,29 +33,27 @@ button->setIcon(QIcon());
//! [2]
-void MyWidget::drawIcon(QPainter *painter, QPoint pos)
+void MyWidget::drawIcon(QPainter *painter, const QRect &rect)
{
- QPixmap pixmap = icon.pixmap(QSize(22, 22),
- isEnabled() ? QIcon::Normal
- : QIcon::Disabled,
- isChecked() ? QIcon::On
- : QIcon::Off);
- painter->drawPixmap(pos, pixmap);
+ icon.paint(painter, rect, Qt::AlignCenter, isEnabled() ? QIcon::Normal
+ : QIcon::Disabled,
+ isChecked() ? QIcon::On
+ : QIcon::Off);
}
//! [2]
void wrapper1() {
-//! [3]
-QIcon undoicon = QIcon::fromTheme("edit-undo");
-//! [3]
+//! [fromTheme]
+QIcon undoicon = QIcon::fromTheme(QIcon::ThemeIcon::EditUndo);
+//! [fromTheme]
} // wrapper1
//! [4]
-QIcon undoicon = QIcon::fromTheme("edit-undo", QIcon(":/undo.png"));
+QIcon undoicon = QIcon::fromTheme(QIcon::ThemeIcon::EditUndo, QIcon(":/undo.png"));
//! [4]
diff --git a/src/gui/doc/snippets/code/src_gui_kernel_qguiapplication.cpp b/src/gui/doc/snippets/code/src_gui_kernel_qguiapplication.cpp
index e9a2446b91..347b47403e 100644
--- a/src/gui/doc/snippets/code/src_gui_kernel_qguiapplication.cpp
+++ b/src/gui/doc/snippets/code/src_gui_kernel_qguiapplication.cpp
@@ -74,26 +74,4 @@ appname -session id
*/ // wrap snippet 2
-
-void wrapper0() {
-
-
-//! [3]
-const QStringList commands = mySession.restartCommand();
-for (const QString &command : commands)
- do_something(command);
-//! [3]
-
-} // wrapper0
-
-
-void wrapper1() {
-//! [4]
-const QStringList commands = mySession.discardCommand();
-for (const QString &command : mySession.discardCommand())
- do_something(command);
-//! [4]
-
-
-} // wrapper1
} // src_gui_kernel_qguiapplication
diff --git a/src/gui/doc/snippets/code/src_gui_painting_qpainter.cpp b/src/gui/doc/snippets/code/src_gui_painting_qpainter.cpp
index 6f52930f73..cfbf3e44a2 100644
--- a/src/gui/doc/snippets/code/src_gui_painting_qpainter.cpp
+++ b/src/gui/doc/snippets/code/src_gui_painting_qpainter.cpp
@@ -63,6 +63,7 @@ struct MyWidget : public QWidget
void wrapper13();
void wrapper14();
void wrapper15();
+ void concentricCircles();
};
QLine drawingCode;
@@ -355,4 +356,21 @@ painter.drawRect(rectangle.adjusted(0, 0, -pen.width(), -pen.width()));
} // MyWidget::wrapper15
-} // src_gui_painting_qpainter2
+
+void MyWidget::concentricCircles()
+{
+//! [renderHint]
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+//! [renderHint]
+ int diameter = 50;
+//! [floatBased]
+ painter.drawEllipse(QRectF(-diameter / 2.0, -diameter / 2.0, diameter, diameter));
+//! [floatBased]
+//! [intBased]
+ painter.drawEllipse(QRect(-diameter / 2, -diameter / 2, diameter, diameter));
+//! [intBased]
+
+} // MyWidget::concentricCircles
+
+} // src_gui_painting_qpainter2 \ No newline at end of file
diff --git a/src/gui/doc/snippets/code/src_gui_vulkan_qvulkanfunctions.cpp b/src/gui/doc/snippets/code/src_gui_vulkan_qvulkanfunctions.cpp
index a83c6eb7f9..700d933f43 100644
--- a/src/gui/doc/snippets/code/src_gui_vulkan_qvulkanfunctions.cpp
+++ b/src/gui/doc/snippets/code/src_gui_vulkan_qvulkanfunctions.cpp
@@ -8,20 +8,18 @@
namespace src_gui_vulkan_qvulkanfunctions {
struct Window {
- void render();
+ void init();
QVulkanInstance *vulkanInstance() { return nullptr; }
};
-VkDevice_T *device = nullptr;
-VkCommandBufferAllocateInfo cmdBufInfo;
-VkCommandBuffer cmdBuf;
//! [0]
-void Window::render()
+void Window::init()
{
QVulkanInstance *inst = vulkanInstance();
QVulkanFunctions *f = inst->functions();
// ...
- VkResult err = f->vkAllocateCommandBuffers(device, &cmdBufInfo, &cmdBuf);
+ uint32_t count = 0;
+ VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &count, nullptr);
// ...
}
//! [0]
diff --git a/src/gui/doc/snippets/rhioffscreen/main.cpp b/src/gui/doc/snippets/rhioffscreen/main.cpp
index b3fac4b520..c2c6f74dc1 100644
--- a/src/gui/doc/snippets/rhioffscreen/main.cpp
+++ b/src/gui/doc/snippets/rhioffscreen/main.cpp
@@ -11,15 +11,17 @@ int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
+#if QT_CONFIG(vulkan)
+ QVulkanInstance inst;
+#endif
std::unique_ptr<QRhi> rhi;
#if defined(Q_OS_WIN)
QRhiD3D12InitParams params;
rhi.reset(QRhi::create(QRhi::D3D12, &params));
-#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#elif QT_CONFIG(metal)
QRhiMetalInitParams params;
rhi.reset(QRhi::create(QRhi::Metal, &params));
#elif QT_CONFIG(vulkan)
- QVulkanInstance inst;
inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions());
if (inst.create()) {
QRhiVulkanInitParams params;
@@ -82,9 +84,7 @@ int main(int argc, char **argv)
ps->setTargetBlends({ premulAlphaBlend });
static auto getShader = [](const QString &name) {
QFile f(name);
- if (f.open(QIODevice::ReadOnly))
- return QShader::fromSerialized(f.readAll());
- return QShader();
+ return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
};
ps->setShaderStages({
{ QRhiShaderStage::Vertex, getShader(QLatin1String("color.vert.qsb")) },
diff --git a/src/gui/doc/snippets/separations/separations.qdoc b/src/gui/doc/snippets/separations/separations.qdoc
index ad670f305a..ee567030bb 100644
--- a/src/gui/doc/snippets/separations/separations.qdoc
+++ b/src/gui/doc/snippets/separations/separations.qdoc
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*
\example painting/separations
diff --git a/src/gui/doc/src/dnd.qdoc b/src/gui/doc/src/dnd.qdoc
index e8276f3465..7a756b304e 100644
--- a/src/gui/doc/src/dnd.qdoc
+++ b/src/gui/doc/src/dnd.qdoc
@@ -335,7 +335,9 @@
For example, we can copy the contents of a QLineEdit to the clipboard
with the following code:
- \snippet ../widgets/widgets/charactermap/mainwindow.cpp 11
+ \code
+ QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);
+ \endcode
Data with different MIME types can also be put on the clipboard.
Construct a QMimeData object and set data with setData() function in
@@ -364,8 +366,6 @@
\li \l{draganddrop/draggableicons}{Draggable Icons}
\li \l{draganddrop/draggabletext}{Draggable Text}
\li \l{draganddrop/dropsite}{Drop Site}
- \li \l{draganddrop/fridgemagnets}{Fridge Magnets}
- \li \l{draganddrop/puzzle}{Drag and Drop Puzzle}
\endlist
\section1 Interoperating with Other Applications
diff --git a/src/gui/doc/src/external-resources.qdoc b/src/gui/doc/src/external-resources.qdoc
index 67ba9f3013..16bca2475d 100644
--- a/src/gui/doc/src/external-resources.qdoc
+++ b/src/gui/doc/src/external-resources.qdoc
@@ -29,15 +29,24 @@
/*!
\externalpage https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
- \title Icon Theme Specification
+ \title Freedesktop Icon Theme Specification
*/
/*!
+ \externalpage https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+ \title Freedesktop Icon Naming Specification
+*/
+/*!
\externalpage https://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout
\title Icon Theme Specification - Directory Layout
*/
/*!
+ \externalpage https://freedesktop.org/
+ \title Freedesktop
+*/
+
+/*!
\externalpage https://www.khronos.org/vulkan/
\title Vulkan
*/
diff --git a/src/gui/doc/src/qt6-changes.qdoc b/src/gui/doc/src/qt6-changes.qdoc
index f4ad767957..60e1bffba8 100644
--- a/src/gui/doc/src/qt6-changes.qdoc
+++ b/src/gui/doc/src/qt6-changes.qdoc
@@ -114,4 +114,38 @@
Metal, in addition to OpenGL. On Windows the default choice is Direct 3D,
therefore the removal of ANGLE is alleviated by having support for graphics
APIs other than OpenGL as well.
+
+ \section2 Native clipboard integration
+
+ Qt 5 provided interfaces for integrating platform specific or custom
+ clipboard formats into Qt through \c QMacPasteboardMime in \c QtMacExtras,
+ and \c QWindowsMime from the Windows QPA API. Since Qt 6.6, the
+ equivalent functionality is provided by the classes QUtiMimeConverter for
+ \macos, and the QWindowsMimeConverter for Windows.
+
+ Porting from QWindowsMime to QWindowsMimeConverter requires practically no
+ changes, as the virtual interface is identical. However, in Qt 6 it is no
+ longer needed to register a QWindowsMimeConverter implementation;
+ instantiating the type implicitly registers the converter.
+
+ Porting a QMacPasteboardMime to QUtiMimeConverter requires renaming some of
+ the virtual functions. Note that the \c{QMacPasteboardMime} API used the
+ outdated term \c{flavor} for the native clipboard format on \macos, whereas
+ the platform now uses \c{Uniform Type Identifiers}, i.e. \c{UTI}s, which Qt
+ has adapted for function and parameter names.
+
+ The \c{mimeFor} and \c{flavorFor} functions are replaced by the
+ \l{QUtiMimeConverter::}{mimeForUti} and \l{QUtiMimeConverter::}{utiForMime}
+ implementations, respectively. Those should return the name of the mime
+ type or \c{UTI} that the converter can convert the input format to, so a
+ port usually just involves renaming existing overrides. The
+ \c{convertToMime}, \c{convertFromMime}, and \c{count} functions in
+ QUtiMimeConverter are identical to their QMacPasteboardMime versions.
+
+ The \c{canConvert}, \c{converterName} functions are no longer needed, they
+ are implied by implementation of the above functions, so overrides of those
+ functions can be removed.
+
+ As with the the QWindowsMimeConverter, registration is done by instantiating
+ the type.
*/
diff --git a/src/gui/doc/src/qtgui-overview.qdoc b/src/gui/doc/src/qtgui-overview.qdoc
index 8ba191d7f0..446479c9be 100644
--- a/src/gui/doc/src/qtgui-overview.qdoc
+++ b/src/gui/doc/src/qtgui-overview.qdoc
@@ -90,6 +90,18 @@
portable, cross-platform application that performs accelerated 3D rendering
onto a QWindow using QRhi.
+ Working directly with QWindow is the most advanced and often the most
+ flexible way of rendering with the QRhi API. It is the most low-level
+ approach, however, and limited in the sense that Qt's UI technologies,
+ widgets and Qt Quick, are not utilized at all. In many cases applications
+ will rather want to integrate QRhi-based rendering into a widget or Qt
+ Quick-based user interface. QWidget-based applications may choose to embed
+ the window as a native child into the widget hierarchy via
+ QWidget::createWindowContainer(), but in many cases \l QRhiWidget will
+ offer a more convenient enabler to integrate QRhi-based rendering into a
+ widget UI. Qt Quick provides its own set of enablers for extending the
+ 2D/3D scene with QRhi-based custom rendering.
+
\note The RHI family of APIs are currently offered with a limited
compatibility guarantee, as opposed to regular Qt public APIs. See \l QRhi
for details.
diff --git a/src/gui/doc/src/qtgui.qdoc b/src/gui/doc/src/qtgui.qdoc
index 4ea352323a..2a1d69d4d3 100644
--- a/src/gui/doc/src/qtgui.qdoc
+++ b/src/gui/doc/src/qtgui.qdoc
@@ -15,6 +15,23 @@
*/
/*!
+ \module QtGuiPrivate
+ \title Qt GUI Private C++ Classes
+ \qtcmakepackage Gui
+ \qtvariable gui-private
+
+ \brief Provides access to private GUI functionality.
+
+ Use the following CMake commands in your \c {CMakeLists.txt} to access
+ private Qt GUI APIs:
+
+ \badcode
+ find_package(Qt6 REQUIRED COMPONENTS Gui)
+ target_link_libraries(mytarget PRIVATE Qt6::GuiPrivate)
+ \endcode
+*/
+
+/*!
\page qtgui-index.html
\title Qt GUI
diff --git a/src/gui/doc/src/richtext.qdoc b/src/gui/doc/src/richtext.qdoc
index 5ae7739481..10d9962850 100644
--- a/src/gui/doc/src/richtext.qdoc
+++ b/src/gui/doc/src/richtext.qdoc
@@ -11,10 +11,9 @@
\page richtext.html
\title Rich Text Processing
\brief An overview of Qt's rich text processing, editing and display features.
-
+ \ingroup explanations-ui
\ingroup frameworks-technologies
\ingroup qt-basic-concepts
- \ingroup best-practices
\nextpage Rich Text Document Structure
@@ -839,7 +838,11 @@
\section1 Supported Tags
The following table lists the HTML tags supported by Qt's
- \l{Rich Text Processing}{rich text} engine:
+ \l{Rich Text Processing}{rich text} engine.
+
+ \note The functionality implemented for tags listed below is a subset of
+ the full HTML 4 specification. Not all attributes are supported,
+ see comments for each tag.
\table 70%
\header \li Tag
diff --git a/src/gui/image/qabstractfileiconengine_p.h b/src/gui/image/qabstractfileiconengine_p.h
index cdabdc7b77..99d16d3224 100644
--- a/src/gui/image/qabstractfileiconengine_p.h
+++ b/src/gui/image/qabstractfileiconengine_p.h
@@ -30,6 +30,7 @@ public:
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State, qreal scale) override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ bool isNull() override { return false; }
QFileInfo fileInfo() const { return m_fileInfo; }
QPlatformTheme::IconOptions options() const { return m_options; }
diff --git a/src/gui/image/qabstractfileiconprovider.cpp b/src/gui/image/qabstractfileiconprovider.cpp
index c5a51001af..71b6a0b03d 100644
--- a/src/gui/image/qabstractfileiconprovider.cpp
+++ b/src/gui/image/qabstractfileiconprovider.cpp
@@ -229,21 +229,16 @@ QIcon QAbstractFileIconProvider::icon(const QFileInfo &info) const
return result.isNull() ? d->getPlatformThemeIcon(info) : result;
}
-/*!
- Returns the type of the file described by \a info.
-*/
-QString QAbstractFileIconProvider::type(const QFileInfo &info) const
+QString QAbstractFileIconProviderPrivate::getFileType(const QFileInfo &info)
{
- Q_D(const QAbstractFileIconProvider);
if (QFileSystemEntry::isRootPath(info.absoluteFilePath()))
return QGuiApplication::translate("QAbstractFileIconProvider", "Drive");
if (info.isFile()) {
#if QT_CONFIG(mimetype)
- const QMimeType mimeType = d->mimeDatabase.mimeTypeForFile(info);
+ const QMimeType mimeType = QMimeDatabase().mimeTypeForFile(info);
return mimeType.comment().isEmpty() ? mimeType.name() : mimeType.comment();
#else
- Q_UNUSED(d);
return QGuiApplication::translate("QAbstractFileIconProvider", "File");
#endif
}
@@ -273,4 +268,13 @@ QString QAbstractFileIconProvider::type(const QFileInfo &info) const
return QGuiApplication::translate("QAbstractFileIconProvider", "Unknown");
}
+/*!
+ Returns the type of the file described by \a info.
+*/
+
+QString QAbstractFileIconProvider::type(const QFileInfo &info) const
+{
+ return QAbstractFileIconProviderPrivate::getFileType(info);
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/image/qabstractfileiconprovider_p.h b/src/gui/image/qabstractfileiconprovider_p.h
index d2caf05440..f53be0f06c 100644
--- a/src/gui/image/qabstractfileiconprovider_p.h
+++ b/src/gui/image/qabstractfileiconprovider_p.h
@@ -37,6 +37,7 @@ public:
QIcon getIconThemeIcon(const QFileInfo &info) const;
static void clearIconTypeCache();
+ static QString getFileType(const QFileInfo &info);
QAbstractFileIconProvider *q_ptr = nullptr;
QAbstractFileIconProvider::Options options = {};
diff --git a/src/gui/image/qbmphandler.cpp b/src/gui/image/qbmphandler.cpp
index b65ca923e5..798ba7f4b3 100644
--- a/src/gui/image/qbmphandler.cpp
+++ b/src/gui/image/qbmphandler.cpp
@@ -608,7 +608,7 @@ bool qt_write_dib(QDataStream &s, const QImage &image, int bpl, int bpl_bmp, int
if (image.depth() != 32) { // write color table
uchar *color_table = new uchar[4*image.colorCount()];
uchar *rgb = color_table;
- QList<QRgb> c = image.colorTable();
+ const QList<QRgb> c = image.colorTable();
for (int i = 0; i < image.colorCount(); i++) {
*rgb++ = qBlue (c[i]);
*rgb++ = qGreen(c[i]);
diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp
index d783c7e84f..82e78409f2 100644
--- a/src/gui/image/qicon.cpp
+++ b/src/gui/image/qicon.cpp
@@ -25,6 +25,7 @@
#include "private/qhexstring_p.h"
#include "private/qguiapplication_p.h"
+#include "private/qoffsetstringarray_p.h"
#include "qpa/qplatformtheme.h"
#ifndef QT_NO_ICON
@@ -159,16 +160,20 @@ static inline int area(const QSize &s) { return s.width() * s.height(); }
// the 2x pixmaps then.)
static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale, QPixmapIconEngineEntry *pa, QPixmapIconEngineEntry *pb)
{
-
+ const auto scaleA = pa->pixmap.devicePixelRatio();
+ const auto scaleB = pb->pixmap.devicePixelRatio();
// scale: we can only differentiate on scale if the scale differs
- if (pa->scale != pb->scale) {
+ if (scaleA != scaleB) {
// Score the pixmaps: 0 is an exact scale match, positive
// scores have more detail than requested, negative scores
// have less detail than requested.
- qreal ascore = pa->scale - scale;
- qreal bscore = pb->scale - scale;
+ qreal ascore = scaleA - scale;
+ qreal bscore = scaleB - scale;
+ // always prefer positive scores to prevent upscaling
+ if ((ascore < 0) != (bscore < 0))
+ return bscore < 0 ? pa : pb;
// Take the one closest to 0
return (qAbs(ascore) < qAbs(bscore)) ? pa : pb;
}
@@ -197,13 +202,14 @@ static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale
QPixmapIconEngineEntry *QPixmapIconEngine::tryMatch(const QSize &size, qreal scale, QIcon::Mode mode, QIcon::State state)
{
QPixmapIconEngineEntry *pe = nullptr;
- for (int i = 0; i < pixmaps.size(); ++i)
- if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) {
+ for (auto &entry : pixmaps) {
+ if (entry.mode == mode && entry.state == state) {
if (pe)
- pe = bestSizeScaleMatch(size, scale, &pixmaps[i], pe);
+ pe = bestSizeScaleMatch(size, scale, &entry, pe);
else
- pe = &pixmaps[i];
+ pe = &entry;
}
+ }
return pe;
}
@@ -274,7 +280,7 @@ QPixmap QPixmapIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIc
pm = pe->pixmap;
if (pm.isNull()) {
- int idx = pixmaps.size();
+ auto idx = pixmaps.size();
while (--idx >= 0) {
if (pe == &pixmaps.at(idx)) {
pixmaps.remove(idx);
@@ -348,15 +354,16 @@ QSize QPixmapIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::
QList<QSize> QPixmapIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state)
{
QList<QSize> sizes;
- for (int i = 0; i < pixmaps.size(); ++i) {
- QPixmapIconEngineEntry &pe = pixmaps[i];
- if (pe.size == QSize() && pe.pixmap.isNull()) {
+ for (QPixmapIconEngineEntry &pe : pixmaps) {
+ if (pe.mode != mode || pe.state != state)
+ continue;
+ if (pe.size.isEmpty() && pe.pixmap.isNull()) {
pe.pixmap = QPixmap(pe.fileName);
pe.size = pe.pixmap.size();
}
- if (pe.mode == mode && pe.state == state && !pe.size.isEmpty())
+ if (!pe.size.isEmpty() && !sizes.contains(pe.size))
sizes.push_back(pe.size);
- }
+ }
return sizes;
}
@@ -364,7 +371,7 @@ void QPixmapIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon
{
if (!pixmap.isNull()) {
QPixmapIconEngineEntry *pe = tryMatch(pixmap.size(), pixmap.devicePixelRatio(), mode, state);
- if (pe && pe->size == pixmap.size() && pe->scale == pixmap.devicePixelRatio()) {
+ if (pe && pe->size == pixmap.size() && pe->pixmap.devicePixelRatio() == pixmap.devicePixelRatio()) {
pe->pixmap = pixmap;
pe->fileName.clear();
} else {
@@ -382,7 +389,7 @@ static inline int origIcoDepth(const QImage &image)
static inline int findBySize(const QList<QImage> &images, const QSize &size)
{
- for (int i = 0; i < images.size(); ++i) {
+ for (qsizetype i = 0; i < images.size(); ++i) {
if (images.at(i).size() == size)
return i;
}
@@ -462,6 +469,11 @@ void QPixmapIconEngine::addFile(const QString &fileName, const QSize &size, QIco
pixmaps += QPixmapIconEngineEntry(abs, size, mode, state);
}
+bool QPixmapIconEngine::isNull()
+{
+ return pixmaps.isEmpty();
+}
+
QString QPixmapIconEngine::key() const
{
return "QPixmapIconEngine"_L1;
@@ -541,15 +553,26 @@ QFactoryLoader *qt_iconEngineFactoryLoader()
A QIcon can generate smaller, larger, active, and disabled pixmaps
from the set of pixmaps it is given. Such pixmaps are used by Qt
- widgets to show an icon representing a particular action.
+ UI components to show an icon representing a particular action.
+
+ \section1 Creating an icon from image files
- The simplest use of QIcon is to create one from a QPixmap file or
- resource, and then use it, allowing Qt to work out all the required
- icon styles and sizes. For example:
+ The simplest way to construct a QIcon is to create one from one or
+ several image files or resources. For example:
\snippet code/src_gui_image_qicon.cpp 0
- To undo a QIcon, simply set a null icon in its place:
+ QIcon can store several images for different states, and Qt will
+ select the image that is the closest match for the action's current
+ state.
+
+ \snippet code/src_gui_image_qicon.cpp addFile
+
+ Qt will generate the required icon styles and sizes when needed,
+ e.g. the pixmap for the QIcon::Disabled state might be generated by
+ graying out one of the provided pixmaps.
+
+ To clear the icon, simply set a null icon in its place:
\snippet code/src_gui_image_qicon.cpp 1
@@ -557,31 +580,54 @@ QFactoryLoader *qt_iconEngineFactoryLoader()
QImageWriter::supportedImageFormats() functions to retrieve a
complete list of the supported file formats.
- When you retrieve a pixmap using pixmap(QSize, Mode, State), and no
- pixmap for this given size, mode and state has been added with
- addFile() or addPixmap(), then QIcon will generate one on the
- fly. This pixmap generation happens in a QIconEngine. The default
- engine scales pixmaps down if required, but never up, and it uses
- the current style to calculate a disabled appearance. By using
- custom icon engines, you can customize every aspect of generated
- icons. With QIconEnginePlugin it is possible to register different
- icon engines for different file suffixes, making it possible for
- third parties to provide additional icon engines to those included
+ \section1 Creating an icon from a theme or icon library
+
+ The most convenient way to construct an icon is by using the
+ \l{QIcon::}{fromTheme()} factory function. Qt implements access to
+ the native icon library on platforms that support the
+ \l {Freedesktop Icon Theme Specification}. Since Qt 6.7, Qt also
+ provides access to the native icon library on macOS, iOS, and
+ Windows 10 and 11. On Android, Qt can access icons from the Material
+ design system as long as the
+ \l{https://github.com/google/material-design-icons/tree/master/font}
+ {MaterialIcons-Regular} font is available on the system, or bundled
+ as a resource at \c{:/qt-project.org/icons/MaterialIcons-Regular.ttf}
+ with the application.
+
+ \snippet code/src_gui_image_qicon.cpp fromTheme
+
+ Applications can use the same theming specification to provide
+ their own icon library. See below for an example theme description
+ and the corresponding directory structure for the image files.
+ Icons from an application-provided theme take precedence over the
+ native icon library.
+
+ In addition, it is possible to provide custom \l {QIconEngine}
+ {icon engines}. This allows applications to customize every aspect
+ of generated icons. With QIconEnginePlugin it is possible to register
+ different icon engines for different file suffixes, making it possible
+ for third parties to provide additional icon engines to those included
with Qt.
- \note Since Qt 4.2, an icon engine that supports SVG is included.
-
\section1 Making Classes that Use QIcon
If you write your own widgets that have an option to set a small
pixmap, consider allowing a QIcon to be set for that pixmap. The
Qt class QToolButton is an example of such a widget.
- Provide a method to set a QIcon, and when you draw the icon, choose
- whichever pixmap is appropriate for the current state of your widget.
- For example:
+ Provide a method to set a QIcon, and paint the QIcon with
+ \l{QIcon::}{paint}, choosing the appropriate parameters based
+ on the current state of your widget. For example:
+
\snippet code/src_gui_image_qicon.cpp 2
+ When you retrieve a pixmap using pixmap(QSize, Mode, State), and no
+ pixmap for this given size, mode and state has been added with
+ addFile() or addPixmap(), then QIcon will generate one on the
+ fly. This pixmap generation happens in a QIconEngine. The default
+ engine scales pixmaps down if required, but never up, and it uses
+ the current style to calculate a disabled appearance.
+
You might also make use of the \c Active mode, perhaps making your
widget \c Active when the mouse is over the widget (see \l
QWidget::enterEvent()), while the mouse is pressed pending the
@@ -595,17 +641,19 @@ QFactoryLoader *qt_iconEngineFactoryLoader()
\section1 High DPI Icons
- There are two ways that QIcon supports \l {High DPI}{high DPI}
- icons: via \l addFile() and \l fromTheme().
+ Icons that are provided by the native icon library are usually based
+ on vector graphics, and will automatically be rendered in the appropriate
+ resolution.
- \l addFile() is useful if you have your own custom directory structure and do
- not need to use the \l {Icon Theme Specification}{freedesktop.org Icon Theme
- Specification}. Icons created via this approach use Qt's \l {High Resolution
- Versions of Images}{"@nx" high DPI syntax}.
+ When providing your own image files via \l addFile(), then QIcon will
+ use Qt's \l {High Resolution Versions of Images}{"@nx" high DPI syntax}.
+ This is useful if you have your own custom directory structure and do not
+ use follow \l {Freedesktop Icon Theme Specification}.
- Using \l fromTheme() is necessary if you plan on following the Icon Theme
- Specification. To make QIcon use the high DPI version of an image, add an
- additional entry to the appropriate \c index.theme file:
+ When providing an application theme, then you need to follow the Icon Theme
+ Specification to specify which files to use for different resolutions.
+ To make QIcon use the high DPI version of an image, add an additional entry
+ to the appropriate \c index.theme file:
\badcode
[Icon Theme]
@@ -637,8 +685,6 @@ QFactoryLoader *qt_iconEngineFactoryLoader()
│ └── appointment-new.png
└── index.theme
\endcode
-
- \sa {Icons Example}
*/
@@ -818,7 +864,9 @@ QPixmap QIcon::pixmap(const QSize &size, Mode mode, State state) const
\since 6.0
Returns a pixmap with the requested \a size, \a devicePixelRatio, \a mode, and \a
- state, generating one if necessary.
+ state, generating one with the given \a mode and \a state if necessary. The pixmap
+ might be smaller than requested, but never larger, unless the device-pixel ratio
+ of the returned pixmap is larger than 1.
\sa actualSize(), paint()
*/
@@ -1123,6 +1171,10 @@ QString QIcon::name() const
\since 4.6
Sets the search paths for icon themes to \a paths.
+
+ The content of \a paths should follow the theme format
+ documented by setThemeName().
+
\sa themeSearchPaths(), fromTheme(), setThemeName()
*/
void QIcon::setThemeSearchPaths(const QStringList &paths)
@@ -1131,20 +1183,14 @@ void QIcon::setThemeSearchPaths(const QStringList &paths)
}
/*!
- \since 4.6
-
- Returns the search paths for icon themes.
-
- The default value will depend on the platform:
+ \since 4.6
- On X11, the search path will use the XDG_DATA_DIRS environment
- variable if available.
+ Returns the search paths for icon themes.
- By default all platforms will have the resource directory
- \c{:\icons} as a fallback. You can use "rcc -project" to generate a
- resource file from your icon theme.
+ The default search paths will be defined by the platform.
+ All platforms will also have the resource directory \c{:\icons} as a fallback.
- \sa setThemeSearchPaths(), fromTheme(), setThemeName()
+ \sa setThemeSearchPaths(), fromTheme(), setThemeName()
*/
QStringList QIcon::themeSearchPaths()
{
@@ -1156,7 +1202,13 @@ QStringList QIcon::themeSearchPaths()
Returns the fallback search paths for icons.
- The default value will depend on the platform.
+ The fallback search paths are consulted for standalone
+ icon files if the \l{themeName()}{current icon theme}
+ or \l{fallbackThemeName()}{fallback icon theme} do
+ not provide results for an icon lookup.
+
+ If not set, the fallback search paths will be defined
+ by the platform.
\sa setFallbackSearchPaths(), themeSearchPaths()
*/
@@ -1170,7 +1222,12 @@ QStringList QIcon::fallbackSearchPaths()
Sets the fallback search paths for icons to \a paths.
- \note To add some path without replacing existing ones:
+ The fallback search paths are consulted for standalone
+ icon files if the \l{themeName()}{current icon theme}
+ or \l{fallbackThemeName()}{fallback icon theme} do
+ not provide results for an icon lookup.
+
+ For example:
\snippet code/src_gui_image_qicon.cpp 5
@@ -1186,11 +1243,15 @@ void QIcon::setFallbackSearchPaths(const QStringList &paths)
Sets the current icon theme to \a name.
- The \a name should correspond to a directory name in the
- themeSearchPath() containing an index.theme
- file describing its contents.
+ The theme will be will be looked up in themeSearchPaths().
- \sa themeSearchPaths(), themeName()
+ At the moment the only supported icon theme format is the
+ \l{Freedesktop Icon Theme Specification}. The \a name should
+ correspond to a directory name in the themeSearchPath()
+ containing an \c index.theme file describing its contents.
+
+ \sa themeSearchPaths(), themeName(),
+ {Freedesktop Icon Theme Specification}
*/
void QIcon::setThemeName(const QString &name)
{
@@ -1202,8 +1263,12 @@ void QIcon::setThemeName(const QString &name)
Returns the name of the current icon theme.
- On X11, the current icon theme depends on your desktop
- settings. On other platforms it is not set by default.
+ If not set, the current icon theme will be defined by the
+ platform.
+
+ \note Platform icon themes are only implemented on
+ \l{Freedesktop} based systems at the moment, and the
+ icon theme depends on your desktop settings.
\sa setThemeName(), themeSearchPaths(), fromTheme(),
hasThemeIcon()
@@ -1218,8 +1283,12 @@ QString QIcon::themeName()
Returns the name of the fallback icon theme.
- On X11, if not set, the fallback icon theme depends on your desktop
- settings. On other platforms it is not set by default.
+ If not set, the fallback icon theme will be defined by the
+ platform.
+
+ \note Platform fallback icon themes are only implemented on
+ \l{Freedesktop} based systems at the moment, and the
+ icon theme depends on your desktop settings.
\sa setFallbackThemeName(), themeName()
*/
@@ -1233,12 +1302,16 @@ QString QIcon::fallbackThemeName()
Sets the fallback icon theme to \a name.
- The \a name should correspond to a directory name in the
- themeSearchPath() containing an index.theme
- file describing its contents.
+ The fallback icon theme is consulted for icons not provided by
+ the \l{themeName()}{current icon theme}, or if the \l{themeName()}
+ {current icon theme} does not exist.
+
+ The \a name should correspond to theme in the same format
+ as documented by setThemeName(), and will be looked up
+ in themeSearchPaths().
- \note This should be done before creating \l QGuiApplication, to ensure
- correct initialization.
+ \note Fallback icon themes should be set before creating
+ QGuiApplication, to ensure correct initialization.
\sa fallbackThemeName(), themeSearchPaths(), themeName()
*/
@@ -1250,34 +1323,25 @@ void QIcon::setFallbackThemeName(const QString &name)
/*!
\since 4.6
- Returns the QIcon corresponding to \a name in the current
- icon theme.
+ Returns the QIcon corresponding to \a name in the
+ \l{themeName()}{current icon theme}.
- The latest version of the freedesktop icon specification and naming
- specification can be obtained here:
-
- \list
- \li \l{http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html}
- \li \l{http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html}
- \endlist
+ If the current theme does not provide an icon for \a name,
+ the \l{fallbackThemeName()}{fallback icon theme} is consulted,
+ before falling back to looking up standalone icon files in the
+ \l{QIcon::fallbackSearchPaths()}{fallback icon search path}.
+ Finally, the platform's native icon library is consulted.
To fetch an icon from the current icon theme:
- \snippet code/src_gui_image_qicon.cpp 3
-
- \note By default, only X11 will support themed icons. In order to
- use themed icons on Mac and Windows, you will have to bundle a
- compliant theme in one of your themeSearchPaths() and set the
- appropriate themeName().
-
- \note Qt will make use of GTK's icon-theme.cache if present to speed up
- the lookup. These caches can be generated using gtk-update-icon-cache:
- \l{https://developer.gnome.org/gtk3/stable/gtk-update-icon-cache.html}.
+ \snippet code/src_gui_image_qicon.cpp fromTheme
- \note If an icon can't be found in the current theme, then it will be
- searched in fallbackSearchPaths() as an unthemed icon.
+ If an \l{themeName()}{icon theme} has not been explicitly
+ set via setThemeName() a platform defined icon theme will
+ be used.
- \sa themeName(), setThemeName(), themeSearchPaths(), fallbackSearchPaths()
+ \sa themeName(), fallbackThemeName(), setThemeName(), themeSearchPaths(), fallbackSearchPaths(),
+ {Freedesktop Icon Naming Specification}
*/
QIcon QIcon::fromTheme(const QString &name)
{
@@ -1285,32 +1349,37 @@ QIcon QIcon::fromTheme(const QString &name)
if (QIcon *cachedIcon = qtIconCache()->object(name))
return *cachedIcon;
- QIcon icon;
- if (QDir::isAbsolutePath(name)) {
+ if (QDir::isAbsolutePath(name))
return QIcon(name);
- } else {
- QPlatformTheme * const platformTheme = QGuiApplicationPrivate::platformTheme();
- bool hasUserTheme = QIconLoader::instance()->hasUserTheme();
- QIconEngine * const engine = (platformTheme && !hasUserTheme) ? platformTheme->createIconEngine(name)
- : new QIconLoaderEngine(name);
- icon = QIcon(engine);
- qtIconCache()->insert(name, new QIcon(icon));
- }
+ QIcon icon(new QThemeIconEngine(name));
+ qtIconCache()->insert(name, new QIcon(icon));
return icon;
}
/*!
\overload
- Returns the QIcon corresponding to \a name in the current
- icon theme. If no such icon is found in the current theme
- \a fallback is returned instead.
+ Returns the QIcon corresponding to \a name in the
+ \l{themeName()}{current icon theme}.
- If you want to provide a guaranteed fallback for platforms that
- do not support theme icons, you can use the second argument:
+ If the current theme does not provide an icon for \a name,
+ the \l{fallbackThemeName()}{fallback icon theme} is consulted,
+ before falling back to looking up standalone icon files in the
+ \l{QIcon::fallbackSearchPaths()}{fallback icon search path}.
+ Finally, the platform's native icon library is consulted.
+
+ If no icon is found \a fallback is returned.
+
+ This is useful to provide a guaranteed fallback, regardless of
+ whether the current set of icon themes and fallbacks paths
+ support the requested icon.
+
+ For example:
\snippet code/src_gui_image_qicon.cpp 4
+
+ \sa fallbackThemeName(), fallbackSearchPaths()
*/
QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
{
@@ -1326,7 +1395,8 @@ QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
\since 4.6
Returns \c true if there is an icon available for \a name in the
- current icon theme, otherwise returns \c false.
+ current icon theme or any of the fallbacks, as described by
+ fromTheme(), otherwise returns \c false.
\sa themeSearchPaths(), fromTheme(), setThemeName()
*/
@@ -1337,6 +1407,401 @@ bool QIcon::hasThemeIcon(const QString &name)
return icon.name() == name;
}
+static constexpr auto themeIconMapping = qOffsetStringArray(
+ "address-book-new",
+ "application-exit",
+ "appointment-new",
+ "call-start",
+ "call-stop",
+ "contact-new",
+ "document-new",
+ "document-open",
+ "document-open-recent",
+ "document-page-setup",
+ "document-print",
+ "document-print-preview",
+ "document-properties",
+ "document-revert",
+ "document-save",
+ "document-save-as",
+ "document-send",
+ "edit-clear",
+ "edit-copy",
+ "edit-cut",
+ "edit-delete",
+ "edit-find",
+ "edit-paste",
+ "edit-redo",
+ "edit-select-all",
+ "edit-undo",
+ "folder-new",
+ "format-indent-less",
+ "format-indent-more",
+ "format-justify-center",
+ "format-justify-fill",
+ "format-justify-left",
+ "format-justify-right",
+ "format-text-direction-ltr",
+ "format-text-direction-rtl",
+ "format-text-bold",
+ "format-text-italic",
+ "format-text-underline",
+ "format-text-strikethrough",
+ "go-down",
+ "go-home",
+ "go-next",
+ "go-previous",
+ "go-up",
+ "help-about",
+ "help-faq",
+ "insert-image",
+ "insert-link",
+ "insert-text",
+ "list-add",
+ "list-remove",
+ "mail-forward",
+ "mail-mark-important",
+ "mail-mark-read",
+ "mail-mark-unread",
+ "mail-message-new",
+ "mail-reply-all",
+ "mail-reply-sender",
+ "mail-send",
+ "media-eject",
+ "media-playback-pause",
+ "media-playback-start",
+ "media-playback-stop",
+ "media-record",
+ "media-seek-backward",
+ "media-seek-forward",
+ "media-skip-backward",
+ "media-skip-forward",
+ "object-rotate-left",
+ "object-rotate-right",
+ "process-stop",
+ "system-lock-screen",
+ "system-log-out",
+ "system-search",
+ "system-reboot",
+ "system-shutdown",
+ "tools-check-spelling",
+ "view-fullscreen",
+ "view-refresh",
+ "view-restore",
+ "window-close",
+ "window-new",
+ "zoom-fit-best",
+ "zoom-in",
+ "zoom-out",
+
+ "audio-card",
+ "audio-input-microphone",
+ "battery",
+ "camera-photo",
+ "camera-video",
+ "camera-web",
+ "computer",
+ "drive-harddisk",
+ "drive-optical",
+ "input-gaming",
+ "input-keyboard",
+ "input-mouse",
+ "input-tablet",
+ "media-flash",
+ "media-optical",
+ "media-tape",
+ "multimedia-player",
+ "network-wired",
+ "network-wireless",
+ "phone",
+ "printer",
+ "scanner",
+ "video-display",
+
+ "appointment-missed",
+ "appointment-soon",
+ "audio-volume-high",
+ "audio-volume-low",
+ "audio-volume-medium",
+ "audio-volume-muted",
+ "battery-caution",
+ "battery-low",
+ "dialog-error",
+ "dialog-information",
+ "dialog-password",
+ "dialog-question",
+ "dialog-warning",
+ "folder-drag-accept",
+ "folder-open",
+ "folder-visiting",
+ "image-loading",
+ "image-missing",
+ "mail-attachment",
+ "mail-unread",
+ "mail-read",
+ "mail-replied",
+ "media-playlist-repeat",
+ "media-playlist-shuffle",
+ "network-offline",
+ "printer-printing",
+ "security-high",
+ "security-low",
+ "software-update-available",
+ "software-update-urgent",
+ "sync-error",
+ "sync-synchronizing",
+ "user-available",
+ "user-offline",
+ "weather-clear",
+ "weather-clear-night",
+ "weather-few-clouds",
+ "weather-few-clouds-night",
+ "weather-fog",
+ "weather-showers",
+ "weather-snow",
+ "weather-storm"
+);
+static_assert(QIcon::ThemeIcon::NThemeIcons == QIcon::ThemeIcon(themeIconMapping.count()));
+
+static constexpr QLatin1StringView themeIconName(QIcon::ThemeIcon icon)
+{
+ using ThemeIconIndex = std::underlying_type_t<QIcon::ThemeIcon>;
+ const auto index = static_cast<ThemeIconIndex>(icon);
+ Q_ASSERT(index < themeIconMapping.count());
+ return QLatin1StringView(themeIconMapping.viewAt(index));
+}
+
+/*!
+ \enum QIcon::ThemeIcon
+ \since 6.7
+
+ This enum provides access to icons that are provided by most
+ icon theme implementations.
+
+ \value AddressBookNew The icon for the action to create a new address book.
+ \value ApplicationExit The icon for exiting an application.
+ \value AppointmentNew The icon for the action to create a new appointment.
+ \value CallStart The icon for initiating or accepting a call.
+ \value CallStop The icon for stopping a current call.
+ \value ContactNew The icon for the action to create a new contact.
+ \value DocumentNew The icon for the action to create a new document.
+ \value DocumentOpen The icon for the action to open a document.
+ \value DocumentOpenRecent The icon for the action to open a document that was recently opened.
+ \value DocumentPageSetup The icon for the \e{page setup} action.
+ \value DocumentPrint The icon for the \e{print} action.
+ \value DocumentPrintPreview The icon for the \e{print preview} action.
+ \value DocumentProperties The icon for the action to view the properties of a document.
+ \value DocumentRevert The icon for the action of reverting to a previous version of a document.
+ \value DocumentSave The icon for the \e{save} action.
+ \value DocumentSaveAs The icon for the \e{save as} action.
+ \value DocumentSend The icon for the \e{send} action.
+ \value EditClear The icon for the \e{clear} action.
+ \value EditCopy The icon for the \e{copy} action.
+ \value EditCut The icon for the \e{cut} action.
+ \value EditDelete The icon for the \e{delete} action.
+ \value EditFind The icon for the \e{find} action.
+ \value EditPaste The icon for the \e{paste} action.
+ \value EditRedo The icon for the \e{redo} action.
+ \value EditSelectAll The icon for the \e{select all} action.
+ \value EditUndo The icon for the \e{undo} action.
+ \value FolderNew The icon for creating a new folder.
+ \value FormatIndentLess The icon for the \e{decrease indent formatting} action.
+ \value FormatIndentMore The icon for the \e{increase indent formatting} action.
+ \value FormatJustifyCenter The icon for the \e{center justification formatting} action.
+ \value FormatJustifyFill The icon for the \e{fill justification formatting} action.
+ \value FormatJustifyLeft The icon for the \e{left justification formatting} action.
+ \value FormatJustifyRight The icon for the \e{right justification} action.
+ \value FormatTextDirectionLtr The icon for the \e{left-to-right text formatting} action.
+ \value FormatTextDirectionRtl The icon for the \e{right-to-left formatting} action.
+ \value FormatTextBold The icon for the \e{bold text formatting} action.
+ \value FormatTextItalic The icon for the \e{italic text formatting} action.
+ \value FormatTextUnderline The icon for the \e{underlined text formatting} action.
+ \value FormatTextStrikethrough The icon for the \e{strikethrough text formatting} action.
+ \value GoDown The icon for the \e{go down in a list} action.
+ \value GoHome The icon for the \e{go to home location} action.
+ \value GoNext The icon for the \e{go to the next item in a list} action.
+ \value GoPrevious The icon for the \e{go to the previous item in a list} action.
+ \value GoUp The icon for the \e{go up in a list} action.
+ \value HelpAbout The icon for the \e{About} item in the Help menu.
+ \value HelpFaq The icon for the \e{FAQ} item in the Help menu.
+ \value InsertImage The icon for the \e{insert image} action of an application.
+ \value InsertLink The icon for the \e{insert link} action of an application.
+ \value InsertText The icon for the \e{insert text} action of an application.
+ \value ListAdd The icon for the \e{add to list} action.
+ \value ListRemove The icon for the \e{remove from list} action.
+ \value MailForward The icon for the \e{forward} action.
+ \value MailMarkImportant The icon for the \e{mark as important} action.
+ \value MailMarkRead The icon for the \e{mark as read} action.
+ \value MailMarkUnread The icon for the \e{mark as unread} action.
+ \value MailMessageNew The icon for the \e{compose new mail} action.
+ \value MailReplyAll The icon for the \e{reply to all} action.
+ \value MailReplySender The icon for the \e{reply to sender} action.
+ \value MailSend The icon for the \e{send} action.
+ \value MediaEject The icon for the \e{eject} action of a media player or file manager.
+ \value MediaPlaybackPause The icon for the \e{pause} action of a media player.
+ \value MediaPlaybackStart The icon for the \e{start playback} action of a media player.
+ \value MediaPlaybackStop The icon for the \e{stop} action of a media player.
+ \value MediaRecord The icon for the \e{record} action of a media application.
+ \value MediaSeekBackward The icon for the \e{seek backward} action of a media player.
+ \value MediaSeekForward The icon for the \e{seek forward} action of a media player.
+ \value MediaSkipBackward The icon for the \e{skip backward} action of a media player.
+ \value MediaSkipForward The icon for the \e{skip forward} action of a media player.
+ \value ObjectRotateLeft The icon for the \e{rotate left} action performed on an object.
+ \value ObjectRotateRight The icon for the \e{rotate right} action performed on an object.
+ \value ProcessStop The icon for the \e{stop action in applications with} actions that
+ may take a while to process, such as web page loading in a browser.
+ \value SystemLockScreen The icon for the \e{lock screen} action.
+ \value SystemLogOut The icon for the \e{log out} action.
+ \value SystemSearch The icon for the \e{search} action.
+ \value SystemReboot The icon for the \e{reboot} action.
+ \value SystemShutdown The icon for the \e{shutdown} action.
+ \value ToolsCheckSpelling The icon for the \e{check spelling} action.
+ \value ViewFullscreen The icon for the \e{fullscreen} action.
+ \value ViewRefresh The icon for the \e{refresh} action.
+ \value ViewRestore The icon for leaving the fullscreen view.
+ \value WindowClose The icon for the \e{close window} action.
+ \value WindowNew The icon for the \e{new window} action.
+ \value ZoomFitBest The icon for the \e{best fit} action.
+ \value ZoomIn The icon for the \e{zoom in} action.
+ \value ZoomOut The icon for the \e{zoom out} action.
+
+ \value AudioCard The icon for the audio rendering device.
+ \value AudioInputMicrophone The icon for the microphone audio input device.
+ \value Battery The icon for the system battery device.
+ \value CameraPhoto The icon for a digital still camera devices.
+ \value CameraVideo The icon for a video camera device.
+ \value CameraWeb The icon for a web camera device.
+ \value Computer The icon for the computing device as a whole.
+ \value DriveHarddisk The icon for hard disk drives.
+ \value DriveOptical The icon for optical media drives such as CD and DVD.
+ \value InputGaming The icon for the gaming input device.
+ \value InputKeyboard The icon for the keyboard input device.
+ \value InputMouse The icon for the mousing input device.
+ \value InputTablet The icon for graphics tablet input devices.
+ \value MediaFlash The icon for flash media, such as a memory stick.
+ \value MediaOptical The icon for physical optical media such as CD and DVD.
+ \value MediaTape The icon for generic physical tape media.
+ \value MultimediaPlayer The icon for generic multimedia playing devices.
+ \value NetworkWired The icon for wired network connections.
+ \value NetworkWireless The icon for wireless network connections.
+ \value Phone The icon for phone devices.
+ \value Printer The icon for a printer device.
+ \value Scanner The icon for a scanner device.
+ \value VideoDisplay The icon for the monitor that video gets displayed on.
+
+ \value AppointmentMissed The icon for when an appointment was missed.
+ \value AppointmentSoon The icon for when an appointment will occur soon.
+ \value AudioVolumeHigh The icon used to indicate high audio volume.
+ \value AudioVolumeLow The icon used to indicate low audio volume.
+ \value AudioVolumeMedium The icon used to indicate medium audio volume.
+ \value AudioVolumeMuted The icon used to indicate the muted state for audio playback.
+ \value BatteryCaution The icon used when the battery is below 40%.
+ \value BatteryLow The icon used when the battery is below 20%.
+ \value DialogError The icon used when a dialog is opened to explain an error
+ condition to the user.
+ \value DialogInformation The icon used when a dialog is opened to give information to the
+ user that may be pertinent to the requested action.
+ \value DialogPassword The icon used when a dialog requesting the authentication
+ credentials for a user is opened.
+ \value DialogQuestion The icon used when a dialog is opened to ask a simple question
+ to the user.
+ \value DialogWarning The icon used when a dialog is opened to warn the user of
+ impending issues with the requested action.
+ \value FolderDragAccept The icon used for a folder while an acceptable object is being
+ dragged onto it.
+ \value FolderOpen The icon used for folders, while their contents are being displayed
+ within the same window.
+ \value FolderVisiting The icon used for folders, while their contents are being displayed
+ in another window.
+ \value ImageLoading The icon used while another image is being loaded.
+ \value ImageMissing The icon used when another image could not be loaded.
+ \value MailAttachment The icon for a message that contains attachments.
+ \value MailUnread The icon for an unread message.
+ \value MailRead The icon for a read message.
+ \value MailReplied The icon for a message that has been replied to.
+ \value MediaPlaylistRepeat The icon for the repeat mode of a media player.
+ \value MediaPlaylistShuffle The icon for the shuffle mode of a media player.
+ \value NetworkOffline The icon used to indicate that the device is not connected to the
+ network.
+ \value PrinterPrinting The icon used while a print job is successfully being spooled to a
+ printing device.
+ \value SecurityHigh The icon used to indicate that the security level of an item is
+ known to be high.
+ \value SecurityLow The icon used to indicate that the security level of an item is
+ known to be low.
+ \value SoftwareUpdateAvailable The icon used to indicate that an update is available.
+ \value SoftwareUpdateUrgent The icon used to indicate that an urgent update is available.
+ \value SyncError The icon used when an error occurs while attempting to synchronize
+ data across devices.
+ \value SyncSynchronizing The icon used while data is successfully synchronizing across
+ devices.
+ \value UserAvailable The icon used to indicate that a user is available.
+ \value UserOffline The icon used to indicate that a user is not available.
+ \value WeatherClear The icon used to indicate that the sky is clear.
+ \value WeatherClearNight The icon used to indicate that the sky is clear
+ during the night.
+ \value WeatherFewClouds The icon used to indicate that the sky is partly cloudy.
+ \value WeatherFewCloudsNight The icon used to indicate that the sky is partly cloudy
+ during the night.
+ \value WeatherFog The icon used to indicate that the weather is foggy.
+ \value WeatherShowers The icon used to indicate that rain showers are occurring.
+ \value WeatherSnow The icon used to indicate that snow is falling.
+ \value WeatherStorm The icon used to indicate that the weather is stormy.
+
+ \omitvalue NThemeIcons
+
+ \sa {QIcon#Creating an icon from a theme or icon library},
+ fromTheme()
+*/
+
+/*!
+ \since 6.7
+ \overload
+
+ Returns \c true if there is an icon available for \a icon in the
+ current icon theme or any of the fallbacks, as described by
+ fromTheme(), otherwise returns \c false.
+
+ \sa fromTheme()
+*/
+bool QIcon::hasThemeIcon(QIcon::ThemeIcon icon)
+{
+ return hasThemeIcon(themeIconName(icon));
+}
+
+/*!
+ \fn QIcon QIcon::fromTheme(QIcon::ThemeIcon icon)
+ \fn QIcon QIcon::fromTheme(QIcon::ThemeIcon icon, const QIcon &fallback)
+ \since 6.7
+ \overload
+
+ Returns the QIcon corresponding to \a icon in the
+ \l{themeName()}{current icon theme}.
+
+ If the current theme does not provide an icon for \a icon,
+ the \l{fallbackThemeName()}{fallback icon theme} is consulted,
+ before falling back to looking up standalone icon files in the
+ \l{QIcon::fallbackSearchPaths()}{fallback icon search path}.
+ Finally, the platform's native icon library is consulted.
+
+ If no icon is found and a \a fallback is provided, \a fallback is
+ returned. This is useful to provide a guaranteed fallback, regardless
+ of whether the current set of icon themes and fallbacks paths
+ support the requested icon.
+
+ If no icon is found and no \a fallback is provided, a default
+ constructed, empty QIcon is returned.
+*/
+QIcon QIcon::fromTheme(QIcon::ThemeIcon icon)
+{
+ return fromTheme(themeIconName(icon));
+}
+
+QIcon QIcon::fromTheme(QIcon::ThemeIcon icon, const QIcon &fallback)
+{
+ return fromTheme(themeIconName(icon), fallback);
+}
+
/*!
\since 5.6
@@ -1431,8 +1896,8 @@ QDataStream &operator>>(QDataStream &s, QIcon &icon)
if (key == "QPixmapIconEngine"_L1) {
icon.d = new QIconPrivate(new QPixmapIconEngine);
icon.d->engine->read(s);
- } else if (key == "QIconLoaderEngine"_L1) {
- icon.d = new QIconPrivate(new QIconLoaderEngine());
+ } else if (key == "QIconLoaderEngine"_L1 || key == "QThemeIconEngine"_L1) {
+ icon.d = new QIconPrivate(new QThemeIconEngine);
icon.d->engine->read(s);
} else {
const int index = iceLoader()->indexOf(key);
diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h
index 22f63b1ecb..5100ada548 100644
--- a/src/gui/image/qicon.h
+++ b/src/gui/image/qicon.h
@@ -22,6 +22,163 @@ public:
enum Mode { Normal, Disabled, Active, Selected };
enum State { On, Off };
+ enum class ThemeIcon {
+ AddressBookNew,
+ ApplicationExit,
+ AppointmentNew,
+ CallStart,
+ CallStop,
+ ContactNew,
+ DocumentNew,
+ DocumentOpen,
+ DocumentOpenRecent,
+ DocumentPageSetup,
+ DocumentPrint,
+ DocumentPrintPreview,
+ DocumentProperties,
+ DocumentRevert,
+ DocumentSave,
+ DocumentSaveAs,
+ DocumentSend,
+ EditClear,
+ EditCopy,
+ EditCut,
+ EditDelete,
+ EditFind,
+ EditPaste,
+ EditRedo,
+ EditSelectAll,
+ EditUndo,
+ FolderNew,
+ FormatIndentLess,
+ FormatIndentMore,
+ FormatJustifyCenter,
+ FormatJustifyFill,
+ FormatJustifyLeft,
+ FormatJustifyRight,
+ FormatTextDirectionLtr,
+ FormatTextDirectionRtl,
+ FormatTextBold,
+ FormatTextItalic,
+ FormatTextUnderline,
+ FormatTextStrikethrough,
+ GoDown,
+ GoHome,
+ GoNext,
+ GoPrevious,
+ GoUp,
+ HelpAbout,
+ HelpFaq,
+ InsertImage,
+ InsertLink,
+ InsertText,
+ ListAdd,
+ ListRemove,
+ MailForward,
+ MailMarkImportant,
+ MailMarkRead,
+ MailMarkUnread,
+ MailMessageNew,
+ MailReplyAll,
+ MailReplySender,
+ MailSend,
+ MediaEject,
+ MediaPlaybackPause,
+ MediaPlaybackStart,
+ MediaPlaybackStop,
+ MediaRecord,
+ MediaSeekBackward,
+ MediaSeekForward,
+ MediaSkipBackward,
+ MediaSkipForward,
+ ObjectRotateLeft,
+ ObjectRotateRight,
+ ProcessStop,
+ SystemLockScreen,
+ SystemLogOut,
+ SystemSearch,
+ SystemReboot,
+ SystemShutdown,
+ ToolsCheckSpelling,
+ ViewFullscreen,
+ ViewRefresh,
+ ViewRestore,
+ WindowClose,
+ WindowNew,
+ ZoomFitBest,
+ ZoomIn,
+ ZoomOut,
+
+ AudioCard,
+ AudioInputMicrophone,
+ Battery,
+ CameraPhoto,
+ CameraVideo,
+ CameraWeb,
+ Computer,
+ DriveHarddisk,
+ DriveOptical,
+ InputGaming,
+ InputKeyboard,
+ InputMouse,
+ InputTablet,
+ MediaFlash,
+ MediaOptical,
+ MediaTape,
+ MultimediaPlayer,
+ NetworkWired,
+ NetworkWireless,
+ Phone,
+ Printer,
+ Scanner,
+ VideoDisplay,
+
+ AppointmentMissed,
+ AppointmentSoon,
+ AudioVolumeHigh,
+ AudioVolumeLow,
+ AudioVolumeMedium,
+ AudioVolumeMuted,
+ BatteryCaution,
+ BatteryLow,
+ DialogError,
+ DialogInformation,
+ DialogPassword,
+ DialogQuestion,
+ DialogWarning,
+ FolderDragAccept,
+ FolderOpen,
+ FolderVisiting,
+ ImageLoading,
+ ImageMissing,
+ MailAttachment,
+ MailUnread,
+ MailRead,
+ MailReplied,
+ MediaPlaylistRepeat,
+ MediaPlaylistShuffle,
+ NetworkOffline,
+ PrinterPrinting,
+ SecurityHigh,
+ SecurityLow,
+ SoftwareUpdateAvailable,
+ SoftwareUpdateUrgent,
+ SyncError,
+ SyncSynchronizing,
+ UserAvailable,
+ UserOffline,
+ WeatherClear,
+ WeatherClearNight,
+ WeatherFewClouds,
+ WeatherFewCloudsNight,
+ WeatherFog,
+ WeatherShowers,
+ WeatherSnow,
+ WeatherStorm,
+
+ NThemeIcons
+ };
+
QIcon() noexcept;
QIcon(const QPixmap &pixmap);
QIcon(const QIcon &other);
@@ -81,6 +238,10 @@ public:
static QIcon fromTheme(const QString &name, const QIcon &fallback);
static bool hasThemeIcon(const QString &name);
+ static QIcon fromTheme(ThemeIcon icon);
+ static QIcon fromTheme(ThemeIcon icon, const QIcon &fallback);
+ static bool hasThemeIcon(ThemeIcon icon);
+
static QStringList themeSearchPaths();
static void setThemeSearchPaths(const QStringList &searchpath);
diff --git a/src/gui/image/qicon_p.h b/src/gui/image/qicon_p.h
index c8d228259e..dfce2d5b53 100644
--- a/src/gui/image/qicon_p.h
+++ b/src/gui/image/qicon_p.h
@@ -49,24 +49,22 @@ public:
struct QPixmapIconEngineEntry
{
- QPixmapIconEngineEntry():scale(1), mode(QIcon::Normal), state(QIcon::Off){}
- QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off)
- :pixmap(pm), size(pm.size()), scale(pm.devicePixelRatio()), mode(m), state(s){}
- QPixmapIconEngineEntry(const QString &file, const QSize &sz = QSize(), QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off)
- :fileName(file), size(sz), scale(1), mode(m), state(s){}
- QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off);
+ QPixmapIconEngineEntry() = default;
+ QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m, QIcon::State s)
+ : pixmap(pm), size(pm.size()), mode(m), state(s) {}
+ QPixmapIconEngineEntry(const QString &file, const QSize &sz, QIcon::Mode m, QIcon::State s)
+ : fileName(file), size(sz), mode(m), state(s) {}
+ QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m, QIcon::State s);
QPixmap pixmap;
QString fileName;
QSize size;
- qreal scale;
- QIcon::Mode mode;
- QIcon::State state;
- bool isNull() const {return (fileName.isEmpty() && pixmap.isNull()); }
+ QIcon::Mode mode = QIcon::Normal;
+ QIcon::State state = QIcon::Off;
};
Q_DECLARE_TYPEINFO(QPixmapIconEngineEntry, Q_RELOCATABLE_TYPE);
inline QPixmapIconEngineEntry::QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m, QIcon::State s)
- : fileName(file), size(image.size()), scale(image.devicePixelRatio()), mode(m), state(s)
+ : fileName(file), size(image.size()), mode(m), state(s)
{
pixmap.convertFromImage(image);
}
@@ -84,7 +82,7 @@ public:
QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) override;
void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) override;
void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state) override;
-
+ bool isNull() override;
QString key() const override;
QIconEngine *clone() const override;
diff --git a/src/gui/image/qiconengine.cpp b/src/gui/image/qiconengine.cpp
index 49528c7034..78273bdeb3 100644
--- a/src/gui/image/qiconengine.cpp
+++ b/src/gui/image/qiconengine.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qiconengine.h"
+#include "qiconengine_p.h"
#include "qpainter.h"
QT_BEGIN_NAMESPACE
@@ -307,4 +308,78 @@ QPixmap QIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::St
return arg.pixmap;
}
+
+// ------- QProxyIconEngine -----
+
+void QProxyIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ proxiedEngine()->paint(painter, rect, mode, state);
+}
+
+QSize QProxyIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return proxiedEngine()->actualSize(size, mode, state);
+}
+
+QPixmap QProxyIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return proxiedEngine()->pixmap(size, mode, state);
+}
+
+void QProxyIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
+{
+ proxiedEngine()->addPixmap(pixmap, mode, state);
+}
+
+void QProxyIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ proxiedEngine()->addFile(fileName, size, mode, state);
+}
+
+QString QProxyIconEngine::key() const
+{
+ return proxiedEngine()->key();
+}
+
+QIconEngine *QProxyIconEngine::clone() const
+{
+ return proxiedEngine()->clone();
+}
+
+bool QProxyIconEngine::read(QDataStream &in)
+{
+ return proxiedEngine()->read(in);
+}
+
+bool QProxyIconEngine::write(QDataStream &out) const
+{
+ return proxiedEngine()->write(out);
+}
+
+QList<QSize> QProxyIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state)
+{
+ return proxiedEngine()->availableSizes(mode, state);
+}
+
+QString QProxyIconEngine::iconName()
+{
+ return proxiedEngine()->iconName();
+}
+
+bool QProxyIconEngine::isNull()
+{
+ return proxiedEngine()->isNull();
+}
+
+QPixmap QProxyIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ return proxiedEngine()->scaledPixmap(size, mode, state, scale);
+}
+
+void QProxyIconEngine::virtual_hook(int id, void *data)
+{
+ proxiedEngine()->virtual_hook(id, data);
+}
+
+
QT_END_NAMESPACE
diff --git a/src/gui/image/qiconengine.h b/src/gui/image/qiconengine.h
index 61411b0660..f5c5184608 100644
--- a/src/gui/image/qiconengine.h
+++ b/src/gui/image/qiconengine.h
@@ -18,6 +18,7 @@ public:
virtual ~QIconEngine();
virtual void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) = 0;
virtual QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state);
+ // ### Qt7: add qreal scale argument and remove scaledPixmap
virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
virtual void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state);
diff --git a/src/gui/image/qiconengine_p.h b/src/gui/image/qiconengine_p.h
new file mode 100644
index 0000000000..3cf0998429
--- /dev/null
+++ b/src/gui/image/qiconengine_p.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QICONENGINE_P_H
+#define QICONENGINE_P_H
+
+#include <QtGui/private/qtguiglobal_p.h>
+
+#ifndef QT_NO_ICON
+//
+// 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/QIcon>
+#include <QtGui/QIconEngine>
+
+QT_BEGIN_NAMESPACE
+
+class QIconEngine;
+
+class QProxyIconEngine : public QIconEngine
+{
+public:
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+
+ void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) override;
+ void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+
+ QString key() const override;
+ QIconEngine *clone() const override;
+ bool read(QDataStream &in) override;
+ bool write(QDataStream &out) const override;
+
+ QList<QSize> availableSizes(QIcon::Mode mode = QIcon::Normal,
+ QIcon::State state = QIcon::Off) override;
+
+ QString iconName() override;
+ bool isNull() override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+
+ void virtual_hook(int id, void *data) override;
+protected:
+ virtual QIconEngine *proxiedEngine() const = 0;
+};
+
+QT_END_NAMESPACE
+
+#endif // QT_NO_ICON
+
+#endif // QICONENGINE_P_H
diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp
index 077eefacb3..982b9a26b4 100644
--- a/src/gui/image/qiconloader.cpp
+++ b/src/gui/image/qiconloader.cpp
@@ -119,27 +119,29 @@ QIconLoader *QIconLoader::instance()
// icons if the theme has changed.
void QIconLoader::updateSystemTheme()
{
- // Only change if this is not explicitly set by the user
- if (m_userTheme.isEmpty()) {
- QString theme = systemThemeName();
- if (theme.isEmpty())
- theme = fallbackThemeName();
- if (theme != m_systemTheme) {
- m_systemTheme = theme;
- qCDebug(lcIconLoader) << "Updated system theme to" << m_systemTheme;
- invalidateKey();
- }
- } else {
- qCDebug(lcIconLoader) << "Ignoring system theme update because"
- << "user theme" << m_userTheme << "has been set";
- }
+ const QString currentSystemTheme = m_systemTheme;
+ m_systemTheme = systemThemeName();
+ if (m_systemTheme.isEmpty())
+ m_systemTheme = systemFallbackThemeName();
+ if (m_systemTheme != currentSystemTheme)
+ qCDebug(lcIconLoader) << "Updated system theme to" << m_systemTheme;
+ // Invalidate even if the system theme name hasn't changed, as the
+ // theme itself may have changed its underlying icon lookup logic.
+ if (!hasUserTheme())
+ invalidateKey();
}
void QIconLoader::invalidateKey()
{
+ // Invalidating the key here will result in QThemeIconEngine
+ // recreating the actual engine the next time the icon is used.
+ // We don't need to clear the QIcon cache itself.
m_themeKey++;
+}
- QIconPrivate::clearIconCache();
+QString QIconLoader::themeName() const
+{
+ return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme;
}
void QIconLoader::setThemeName(const QString &themeName)
@@ -149,7 +151,13 @@ void QIconLoader::setThemeName(const QString &themeName)
qCDebug(lcIconLoader) << "Setting user theme name to" << themeName;
+ const bool hadUserTheme = hasUserTheme();
m_userTheme = themeName;
+ // if we cleared the user theme, then reset search paths as well,
+ // otherwise we'll keep looking in the user-defined search paths for
+ // a system-provide theme, which will never work.
+ if (!hasUserTheme() && hadUserTheme)
+ setThemeSearchPath(systemIconSearchPaths());
invalidateKey();
}
@@ -162,6 +170,7 @@ void QIconLoader::setFallbackThemeName(const QString &themeName)
{
qCDebug(lcIconLoader) << "Setting fallback theme name to" << themeName;
m_userFallbackTheme = themeName;
+ invalidateKey();
}
void QIconLoader::setThemeSearchPath(const QStringList &searchPaths)
@@ -349,12 +358,12 @@ QIconTheme::QIconTheme(const QString &themeName)
if (!m_valid) {
themeIndex.setFileName(themeDir + "/index.theme"_L1);
- if (themeIndex.exists())
- m_valid = true;
+ m_valid = themeIndex.exists();
+ qCDebug(lcIconLoader) << "Probing theme file at" << themeIndex.fileName() << m_valid;
}
}
#if QT_CONFIG(settings)
- if (themeIndex.exists()) {
+ if (m_valid) {
const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
const QStringList keys = indexReader.allKeys();
for (const QString &key : keys) {
@@ -383,6 +392,17 @@ QIconTheme::QIconTheme(const QString &themeName)
dirInfo.maxSize = indexReader.value(directoryKey + "/MaxSize"_L1, size).toInt();
dirInfo.scale = indexReader.value(directoryKey + "/Scale"_L1, 1).toInt();
+
+ const QString context = indexReader.value(directoryKey + "/Context"_L1).toString();
+ dirInfo.context = [context]() {
+ if (context == "Applications"_L1)
+ return QIconDirInfo::Applications;
+ else if (context == "MimeTypes"_L1)
+ return QIconDirInfo::MimeTypes;
+ else
+ return QIconDirInfo::UnknownContext;
+ }();
+
m_keyList.append(dirInfo);
}
}
@@ -391,21 +411,27 @@ QIconTheme::QIconTheme(const QString &themeName)
// Parent themes provide fallbacks for missing icons
m_parents = indexReader.value("Icon Theme/Inherits"_L1).toStringList();
m_parents.removeAll(QString());
-
- // Ensure a default platform fallback for all themes
- if (m_parents.isEmpty()) {
- const QString fallback = QIconLoader::instance()->fallbackThemeName();
- if (!fallback.isEmpty())
- m_parents.append(fallback);
- }
-
- // Ensure that all themes fall back to hicolor
- if (!m_parents.contains("hicolor"_L1))
- m_parents.append("hicolor"_L1);
}
#endif // settings
}
+QStringList QIconTheme::parents() const
+{
+ // Respect explicitly declared parents
+ QStringList result = m_parents;
+
+ // Ensure a default fallback for all themes
+ const QString fallback = QIconLoader::instance()->fallbackThemeName();
+ if (!fallback.isEmpty())
+ result.append(fallback);
+
+ // Ensure that all themes fall back to hicolor as the last theme
+ result.removeAll("hicolor"_L1);
+ result.append("hicolor"_L1);
+
+ return result;
+}
+
QDebug operator<<(QDebug debug, const std::unique_ptr<QIconLoaderEngineEntry> &entry)
{
QDebugStateSaver saver(debug);
@@ -415,9 +441,11 @@ QDebug operator<<(QDebug debug, const std::unique_ptr<QIconLoaderEngineEntry> &e
QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
const QString &iconName,
- QStringList &visited) const
+ QStringList &visited,
+ DashRule rule) const
{
- qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName;
+ qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName
+ << "skipping" << visited;
QThemeIconInfo info;
Q_ASSERT(!themeName.isEmpty());
@@ -429,18 +457,18 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
if (!theme.isValid()) {
theme = QIconTheme(themeName);
if (!theme.isValid()) {
- qCDebug(lcIconLoader) << "Theme" << themeName << "not found;"
- << "trying fallback theme" << fallbackThemeName();
- theme = QIconTheme(fallbackThemeName());
+ qCDebug(lcIconLoader) << "Theme" << themeName << "not found";
+ return info;
}
}
const QStringList contentDirs = theme.contentDirs();
QStringView iconNameFallback(iconName);
+ bool searchingGenericFallback = m_iconName.length() > iconName.length();
// Iterate through all icon's fallbacks in current theme
- while (info.entries.empty()) {
+ if (info.entries.empty()) {
const QString svgIconName = iconNameFallback + ".svg"_L1;
const QString pngIconName = iconNameFallback + ".png"_L1;
@@ -472,6 +500,11 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
QString contentDir = contentDirs.at(i) + u'/';
for (int j = 0; j < subDirs.size() ; ++j) {
const QIconDirInfo &dirInfo = subDirs.at(j);
+ if (searchingGenericFallback &&
+ (dirInfo.context == QIconDirInfo::Applications ||
+ dirInfo.context == QIconDirInfo::MimeTypes))
+ continue;
+
const QString subDir = contentDir + dirInfo.path + u'/';
const QString pngPath = subDir + pngIconName;
if (QFile::exists(pngPath)) {
@@ -495,15 +528,7 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
if (!info.entries.empty()) {
info.iconName = iconNameFallback.toString();
- break;
}
-
- // If it's possible - find next fallback for the icon
- const int indexOfDash = iconNameFallback.lastIndexOf(u'-');
- if (indexOfDash == -1)
- break;
-
- iconNameFallback.truncate(indexOfDash);
}
if (info.entries.empty()) {
@@ -518,13 +543,25 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
const QString parentTheme = parents.at(i).trimmed();
if (!visited.contains(parentTheme)) // guard against recursion
- info = findIconHelper(parentTheme, iconName, visited);
+ info = findIconHelper(parentTheme, iconName, visited, QIconLoader::NoFallBack);
if (!info.entries.empty()) // success
break;
}
}
+ if (rule == QIconLoader::FallBack && info.entries.empty()) {
+ // If it's possible - find next fallback for the icon
+ const int indexOfDash = iconNameFallback.lastIndexOf(u'-');
+ if (indexOfDash != -1) {
+ qCDebug(lcIconLoader) << "Did not find matching icons in all themes;"
+ << "trying dash fallback";
+ iconNameFallback.truncate(indexOfDash);
+ QStringList _visited;
+ info = findIconHelper(themeName, iconNameFallback.toString(), _visited, QIconLoader::FallBack);
+ }
+ }
+
return info;
}
@@ -572,64 +609,160 @@ QThemeIconInfo QIconLoader::loadIcon(const QString &name) const
{
qCDebug(lcIconLoader) << "Loading icon" << name;
+ m_iconName = name;
QThemeIconInfo iconInfo;
- if (!themeName().isEmpty()) {
- QStringList visited;
- iconInfo = findIconHelper(themeName(), name, visited);
- if (iconInfo.entries.empty())
- iconInfo = lookupFallbackIcon(name);
- }
+ QStringList visitedThemes;
+ if (!themeName().isEmpty())
+ iconInfo = findIconHelper(themeName(), name, visitedThemes, QIconLoader::FallBack);
+
+ if (iconInfo.entries.empty() && !fallbackThemeName().isEmpty())
+ iconInfo = findIconHelper(fallbackThemeName(), name, visitedThemes, QIconLoader::FallBack);
+
+ if (iconInfo.entries.empty())
+ iconInfo = lookupFallbackIcon(name);
qCDebug(lcIconLoader) << "Resulting icon entries" << iconInfo.entries;
return iconInfo;
}
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, QIconEngine *engine)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ if (engine) {
+ debug.noquote() << engine->key() << "(";
+ debug << static_cast<const void *>(engine);
+ if (!engine->isNull())
+ debug.quote() << ", " << engine->iconName();
+ else
+ debug << ", null";
+ debug << ")";
+ } else {
+ debug << "QIconEngine(nullptr)";
+ }
+ return debug;
+}
+#endif
+
+QIconEngine *QIconLoader::iconEngine(const QString &iconName) const
+{
+ qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName;
+
+ std::unique_ptr<QIconEngine> iconEngine;
+ if (hasUserTheme())
+ iconEngine.reset(new QIconLoaderEngine(iconName));
+ if (!iconEngine || iconEngine->isNull()) {
+ qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme.";
+ if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
+ qCDebug(lcIconLoader) << "Trying platform engine.";
+ std::unique_ptr<QIconEngine> themeEngine(platformTheme->createIconEngine(iconName));
+ if (themeEngine && !themeEngine->isNull()) {
+ iconEngine = std::move(themeEngine);
+ qCDebug(lcIconLoader) << "Icon provided by platform engine.";
+ }
+ }
+ }
+ // We need to maintain the invariant that the QIcon has a valid engine
+ if (!iconEngine)
+ iconEngine.reset(new QIconLoaderEngine(iconName));
-// -------- Icon Loader Engine -------- //
+ qCDebug(lcIconLoader) << "Resulting engine" << iconEngine.get();
+ return iconEngine.release();
+}
+/*!
+ \internal
+ \class QThemeIconEngine
+ \inmodule QtGui
-QIconLoaderEngine::QIconLoaderEngine(const QString& iconName)
- : m_iconName(iconName), m_key(0)
+ \brief A named-based icon engine for providing theme icons.
+
+ The engine supports invalidation of prior lookups, e.g. when
+ the platform theme changes or the user sets an explicit icon
+ theme.
+
+ The actual icon lookup is handed over to an engine provided
+ by QIconLoader::iconEngine().
+*/
+
+QThemeIconEngine::QThemeIconEngine(const QString& iconName)
+ : QProxyIconEngine()
+ , m_iconName(iconName)
{
}
-QIconLoaderEngine::~QIconLoaderEngine() = default;
+QThemeIconEngine::QThemeIconEngine(const QThemeIconEngine &other)
+ : QProxyIconEngine()
+ , m_iconName(other.m_iconName)
+{
+}
-QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other)
- : QIconEngine(other),
- m_iconName(other.m_iconName),
- m_key(0)
+QString QThemeIconEngine::key() const
{
+ // Although we proxy the underlying engine, that's an implementation
+ // detail, so from the point of view of QIcon, and in terms of
+ // serialization, we are the one and only theme icon engine.
+ return u"QThemeIconEngine"_s;
}
-QIconEngine *QIconLoaderEngine::clone() const
+QIconEngine *QThemeIconEngine::clone() const
{
- return new QIconLoaderEngine(*this);
+ return new QThemeIconEngine(*this);
}
-bool QIconLoaderEngine::read(QDataStream &in) {
+bool QThemeIconEngine::read(QDataStream &in) {
in >> m_iconName;
return true;
}
-bool QIconLoaderEngine::write(QDataStream &out) const
+bool QThemeIconEngine::write(QDataStream &out) const
{
out << m_iconName;
return true;
}
-bool QIconLoaderEngine::hasIcon() const
+QIconEngine *QThemeIconEngine::proxiedEngine() const
{
- return !(m_info.entries.empty());
+ const auto *iconLoader = QIconLoader::instance();
+ auto mostRecentThemeKey = iconLoader->themeKey();
+ if (mostRecentThemeKey != m_themeKey) {
+ qCDebug(lcIconLoader) << "Theme key" << mostRecentThemeKey << "is different"
+ << "than cached key" << m_themeKey << "for icon" << m_iconName;
+ m_proxiedEngine.reset(iconLoader->iconEngine(m_iconName));
+ m_themeKey = mostRecentThemeKey;
+ }
+ return m_proxiedEngine.get();
}
-// Lazily load the icon
-void QIconLoaderEngine::ensureLoaded()
+/*!
+ \internal
+ \class QIconLoaderEngine
+ \inmodule QtGui
+
+ \brief An icon engine based on icon entries collected by QIconLoader.
+
+ The design and implementation of QIconLoader is based on
+ the XDG icon specification.
+*/
+
+QIconLoaderEngine::QIconLoaderEngine(const QString& iconName)
+ : m_iconName(iconName)
+ , m_info(QIconLoader::instance()->loadIcon(m_iconName))
{
- if (QIconLoader::instance()->themeKey() != m_key) {
- m_info = QIconLoader::instance()->loadIcon(m_iconName);
- m_key = QIconLoader::instance()->themeKey();
- }
+}
+
+QIconLoaderEngine::~QIconLoaderEngine() = default;
+
+QIconEngine *QIconLoaderEngine::clone() const
+{
+ Q_UNREACHABLE();
+ return nullptr; // Cannot be cloned
+}
+
+bool QIconLoaderEngine::hasIcon() const
+{
+ return !(m_info.entries.empty());
}
void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect,
@@ -737,8 +870,6 @@ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
Q_UNUSED(mode);
Q_UNUSED(state);
- ensureLoaded();
-
QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
if (entry) {
const QIconDirInfo &dir = entry->dir;
@@ -747,7 +878,7 @@ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
} else if (dir.type == QIconDirInfo::Fallback) {
return QIcon(entry->filename).actualSize(size, mode, state);
} else {
- int result = qMin<int>(dir.size, qMin(size.width(), size.height()));
+ int result = qMin<int>(dir.size * dir.scale, qMin(size.width(), size.height()));
return QSize(result, result);
}
}
@@ -807,8 +938,6 @@ QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State
QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode,
QIcon::State state)
{
- ensureLoaded();
-
QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
if (entry)
return entry->pixmap(size, mode, state);
@@ -823,19 +952,16 @@ QString QIconLoaderEngine::key() const
QString QIconLoaderEngine::iconName()
{
- ensureLoaded();
return m_info.iconName;
}
bool QIconLoaderEngine::isNull()
{
- ensureLoaded();
return m_info.entries.empty();
}
QPixmap QIconLoaderEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
{
- ensureLoaded();
const int integerScale = qCeil(scale);
QIconLoaderEngineEntry *entry = entryForSize(m_info, size / integerScale, integerScale);
return entry ? entry->pixmap(size, mode, state) : QPixmap();
@@ -845,7 +971,7 @@ QList<QSize> QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State st
{
Q_UNUSED(mode);
Q_UNUSED(state);
- ensureLoaded();
+
const qsizetype N = qsizetype(m_info.entries.size());
QList<QSize> sizes;
sizes.reserve(N);
diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h
index fbb21073ce..3cfa9381d1 100644
--- a/src/gui/image/qiconloader_p.h
+++ b/src/gui/image/qiconloader_p.h
@@ -22,6 +22,7 @@
#include <QtGui/QIconEngine>
#include <QtGui/QPixmapCache>
#include <private/qicon_p.h>
+#include <private/qiconengine_p.h>
#include <private/qfactoryloader_p.h>
#include <QtCore/QHash>
#include <QtCore/QList>
@@ -37,6 +38,7 @@ class QIconLoader;
struct QIconDirInfo
{
enum Type { Fixed, Scalable, Threshold, Fallback };
+ enum Context { UnknownContext, Applications, MimeTypes };
QIconDirInfo(const QString &_path = QString()) :
path(_path),
size(0),
@@ -44,7 +46,8 @@ struct QIconDirInfo
minSize(0),
threshold(0),
scale(1),
- type(Threshold) {}
+ type(Threshold),
+ context(UnknownContext) {}
QString path;
short size;
short maxSize;
@@ -52,6 +55,7 @@ struct QIconDirInfo
short threshold;
short scale;
Type type;
+ Context context;
};
Q_DECLARE_TYPEINFO(QIconDirInfo, Q_RELOCATABLE_TYPE);
@@ -86,6 +90,27 @@ struct QThemeIconInfo
QString iconName;
};
+class QThemeIconEngine : public QProxyIconEngine
+{
+public:
+ QThemeIconEngine(const QString& iconName = QString());
+ QIconEngine *clone() const override;
+ bool read(QDataStream &in) override;
+ bool write(QDataStream &out) const override;
+
+protected:
+ QIconEngine *proxiedEngine() const override;
+
+private:
+ QThemeIconEngine(const QThemeIconEngine &other);
+ QString key() const override;
+
+ QString m_iconName;
+ mutable uint m_themeKey = 0;
+
+ mutable std::unique_ptr<QIconEngine> m_proxiedEngine;
+};
+
class QIconLoaderEngine : public QIconEngine
{
public:
@@ -96,8 +121,6 @@ public:
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QIconEngine *clone() const override;
- bool read(QDataStream &in) override;
- bool write(QDataStream &out) const override;
QString iconName() override;
bool isNull() override;
@@ -107,14 +130,13 @@ public:
Q_GUI_EXPORT static QIconLoaderEngineEntry *entryForSize(const QThemeIconInfo &info, const QSize &size, int scale = 1);
private:
+ Q_DISABLE_COPY(QIconLoaderEngine)
+
QString key() const override;
bool hasIcon() const;
- void ensureLoaded();
- QIconLoaderEngine(const QIconLoaderEngine &other);
- QThemeIconInfo m_info;
QString m_iconName;
- uint m_key;
+ QThemeIconInfo m_info;
friend class QIconLoader;
};
@@ -126,7 +148,7 @@ class QIconTheme
public:
QIconTheme(const QString &name);
QIconTheme() : m_valid(false) {}
- QStringList parents() { return m_parents; }
+ QStringList parents() const;
QList<QIconDirInfo> keyList() { return m_keyList; }
QStringList contentDirs() { return m_contentDirs; }
bool isValid() { return m_valid; }
@@ -146,7 +168,7 @@ public:
QThemeIconInfo loadIcon(const QString &iconName) const;
uint themeKey() const { return m_themeKey; }
- QString themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; }
+ QString themeName() const;
void setThemeName(const QString &themeName);
QString fallbackThemeName() const;
void setFallbackThemeName(const QString &themeName);
@@ -162,10 +184,14 @@ public:
void ensureInitialized();
bool hasUserTheme() const { return !m_userTheme.isEmpty(); }
+ QIconEngine *iconEngine(const QString &iconName) const;
+
private:
+ enum DashRule { FallBack, NoFallBack };
QThemeIconInfo findIconHelper(const QString &themeName,
const QString &iconName,
- QStringList &visited) const;
+ QStringList &visited,
+ DashRule rule) const;
QThemeIconInfo lookupFallbackIcon(const QString &iconName) const;
uint m_themeKey;
@@ -178,6 +204,7 @@ private:
mutable QStringList m_iconDirs;
mutable QHash <QString, QIconTheme> themeList;
mutable QStringList m_fallbackDirs;
+ mutable QString m_iconName;
};
QT_END_NAMESPACE
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp
index 4aff92ac80..a24d7ccde7 100644
--- a/src/gui/image/qimage.cpp
+++ b/src/gui/image/qimage.cpp
@@ -21,6 +21,7 @@
#include <stdlib.h>
#include <limits.h>
#include <qpa/qplatformpixmap.h>
+#include <private/qcolorspace_p.h>
#include <private/qcolortransform_p.h>
#include <private/qmemrotate_p.h>
#include <private/qimagescale_p.h>
@@ -45,6 +46,7 @@
#include <memory>
QT_BEGIN_NAMESPACE
+class QCmyk32;
using namespace Qt::StringLiterals;
@@ -304,6 +306,7 @@ bool QImageData::checkForAlphaPixels() const
case QImage::Format_RGBX64:
case QImage::Format_RGBX16FPx4:
case QImage::Format_RGBX32FPx4:
+ case QImage::Format_CMYK8888:
break;
case QImage::Format_Invalid:
case QImage::NImageFormats:
@@ -360,7 +363,7 @@ bool QImageData::checkForAlphaPixels() const
refer to the \l{How to Create Qt Plugins}{Plugin HowTo}.
\warning Painting on a QImage with the format
- QImage::Format_Indexed8 is not supported.
+ QImage::Format_Indexed8 or QImage::Format_CMYK8888 is not supported.
\tableofcontents
@@ -616,8 +619,8 @@ bool QImageData::checkForAlphaPixels() const
\endtable
- \sa QImageReader, QImageWriter, QPixmap, QSvgRenderer, {Image Composition Example},
- {Image Viewer Example}, {Scribble Example}, {Pixelator Example}
+ \sa QImageReader, QImageWriter, QPixmap, QSvgRenderer,
+ {Image Composition Example}, {Scribble Example}
*/
/*!
@@ -710,40 +713,62 @@ bool QImageData::checkForAlphaPixels() const
The unused bits are always zero.
\value Format_ARGB4444_Premultiplied The image is stored using a
premultiplied 16-bit ARGB format (4-4-4-4).
- \value Format_RGBX8888 The image is stored using a 32-bit byte-ordered RGB(x) format (8-8-8-8).
- This is the same as the Format_RGBA8888 except alpha must always be 255. (added in Qt 5.2)
- \value Format_RGBA8888 The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8).
+ \value [since 5.2]
+ Format_RGBX8888 The image is stored using a 32-bit byte-ordered RGB(x) format (8-8-8-8).
+ This is the same as the Format_RGBA8888 except alpha must always be 255.
+ \value [since 5.2]
+ Format_RGBA8888 The image is stored using a 32-bit byte-ordered RGBA format (8-8-8-8).
Unlike ARGB32 this is a byte-ordered format, which means the 32bit
encoding differs between big endian and little endian architectures,
being respectively (0xRRGGBBAA) and (0xAABBGGRR). The order of the colors
- is the same on any architecture if read as bytes 0xRR,0xGG,0xBB,0xAA. (added in Qt 5.2)
- \value Format_RGBA8888_Premultiplied The image is stored using a
- premultiplied 32-bit byte-ordered RGBA format (8-8-8-8). (added in Qt 5.2)
- \value Format_BGR30 The image is stored using a 32-bit BGR format (x-10-10-10). (added in Qt 5.4)
- \value Format_A2BGR30_Premultiplied The image is stored using a 32-bit premultiplied ABGR format (2-10-10-10). (added in Qt 5.4)
- \value Format_RGB30 The image is stored using a 32-bit RGB format (x-10-10-10). (added in Qt 5.4)
- \value Format_A2RGB30_Premultiplied The image is stored using a 32-bit premultiplied ARGB format (2-10-10-10). (added in Qt 5.4)
- \value Format_Alpha8 The image is stored using an 8-bit alpha only format. (added in Qt 5.5)
- \value Format_Grayscale8 The image is stored using an 8-bit grayscale format. (added in Qt 5.5)
- \value Format_Grayscale16 The image is stored using an 16-bit grayscale format. (added in Qt 5.13)
- \value Format_RGBX64 The image is stored using a 64-bit halfword-ordered RGB(x) format (16-16-16-16).
- This is the same as the Format_RGBA64 except alpha must always be 65535. (added in Qt 5.12)
- \value Format_RGBA64 The image is stored using a 64-bit halfword-ordered RGBA format (16-16-16-16). (added in Qt 5.12)
- \value Format_RGBA64_Premultiplied The image is stored using a premultiplied 64-bit halfword-ordered
- RGBA format (16-16-16-16). (added in Qt 5.12)
- \value Format_BGR888 The image is stored using a 24-bit BGR format. (added in Qt 5.14)
- \value Format_RGBX16FPx4 The image is stored using a 4 16-bit halfword floating point RGBx format (16FP-16FP-16FP-16FP).
- This is the same as the Format_RGBA16FPx4 except alpha must always be 1.0. (added in Qt 6.2)
- \value Format_RGBA16FPx4 The image is stored using a 4 16-bit halfword floating point RGBA format (16FP-16FP-16FP-16FP). (added in Qt 6.2)
- \value Format_RGBA16FPx4_Premultiplied The image is stored using a premultiplied 4 16-bit halfword floating point
- RGBA format (16FP-16FP-16FP-16FP). (added in Qt 6.2)
- \value Format_RGBX32FPx4 The image is stored using a 4 32-bit floating point RGBx format (32FP-32FP-32FP-32FP).
- This is the same as the Format_RGBA32FPx4 except alpha must always be 1.0. (added in Qt 6.2)
- \value Format_RGBA32FPx4 The image is stored using a 4 32-bit floating point RGBA format (32FP-32FP-32FP-32FP). (added in Qt 6.2)
- \value Format_RGBA32FPx4_Premultiplied The image is stored using a premultiplied 4 32-bit floating point
- RGBA format (32FP-32FP-32FP-32FP). (added in Qt 6.2)
-
- \note Drawing into a QImage with QImage::Format_Indexed8 is not
+ is the same on any architecture if read as bytes 0xRR,0xGG,0xBB,0xAA.
+ \value [since 5.2]
+ Format_RGBA8888_Premultiplied The image is stored using a
+ premultiplied 32-bit byte-ordered RGBA format (8-8-8-8).
+ \value [since 5.4]
+ Format_BGR30 The image is stored using a 32-bit BGR format (x-10-10-10).
+ \value [since 5.4]
+ Format_A2BGR30_Premultiplied The image is stored using a 32-bit premultiplied ABGR format (2-10-10-10).
+ \value [since 5.4]
+ Format_RGB30 The image is stored using a 32-bit RGB format (x-10-10-10).
+ \value [since 5.4]
+ Format_A2RGB30_Premultiplied The image is stored using a 32-bit premultiplied ARGB format (2-10-10-10).
+ \value [since 5.5]
+ Format_Alpha8 The image is stored using an 8-bit alpha only format.
+ \value [since 5.5]
+ Format_Grayscale8 The image is stored using an 8-bit grayscale format.
+ \value [since 5.13]
+ Format_Grayscale16 The image is stored using an 16-bit grayscale format.
+ \value [since 5.12]
+ Format_RGBX64 The image is stored using a 64-bit halfword-ordered RGB(x) format (16-16-16-16).
+ This is the same as the Format_RGBA64 except alpha must always be 65535.
+ \value [since 5.12]
+ Format_RGBA64 The image is stored using a 64-bit halfword-ordered RGBA format (16-16-16-16).
+ \value [since 5.12]
+ Format_RGBA64_Premultiplied The image is stored using a premultiplied 64-bit halfword-ordered
+ RGBA format (16-16-16-16).
+ \value [since 5.14]
+ Format_BGR888 The image is stored using a 24-bit BGR format.
+ \value [since 6.2]
+ Format_RGBX16FPx4 The image is stored using a four 16-bit halfword floating point RGBx format (16FP-16FP-16FP-16FP).
+ This is the same as the Format_RGBA16FPx4 except alpha must always be 1.0.
+ \value [since 6.2]
+ Format_RGBA16FPx4 The image is stored using a four 16-bit halfword floating point RGBA format (16FP-16FP-16FP-16FP).
+ \value [since 6.2]
+ Format_RGBA16FPx4_Premultiplied The image is stored using a premultiplied four 16-bit halfword floating point
+ RGBA format (16FP-16FP-16FP-16FP).
+ \value [since 6.2]
+ Format_RGBX32FPx4 The image is stored using a four 32-bit floating point RGBx format (32FP-32FP-32FP-32FP).
+ This is the same as the Format_RGBA32FPx4 except alpha must always be 1.0.
+ \value [since 6.2]
+ Format_RGBA32FPx4 The image is stored using a four 32-bit floating point RGBA format (32FP-32FP-32FP-32FP).
+ \value [since 6.2]
+ Format_RGBA32FPx4_Premultiplied The image is stored using a premultiplied four 32-bit floating point
+ RGBA format (32FP-32FP-32FP-32FP).
+ \value [since 6.8]
+ Format_CMYK8888 The image is stored using a 32-bit byte-ordered CMYK format.
+
+ \note Drawing into a QImage with format QImage::Format_Indexed8 or QImage::Format_CMYK8888 is not
supported.
\note Avoid most rendering directly to most of these formats using QPainter. Rendering
@@ -819,7 +844,7 @@ QImageData *QImageData::create(uchar *data, int width, int height, qsizetype bp
// recalculate the total with this value
params.bytesPerLine = bpl;
- if (mul_overflow<qsizetype>(bpl, height, &params.totalSize))
+ if (qMulOverflow<qsizetype>(bpl, height, &params.totalSize))
return nullptr;
}
@@ -1068,7 +1093,6 @@ QImage &QImage::operator=(const QImage &image)
/*!
\fn void QImage::swap(QImage &other)
- \since 4.8
Swaps image \a other with this image. This operation is very
fast and never fails.
@@ -1147,9 +1171,10 @@ static void copyPhysicalMetadata(QImageData *dst, const QImageData *src)
static void copyMetadata(QImageData *dst, const QImageData *src)
{
- // Doesn't copy colortable and alpha_clut, or offset.
+ // Doesn't copy colortable and alpha_clut.
copyPhysicalMetadata(dst, src);
dst->text = src->text;
+ dst->offset = src->offset;
dst->colorSpace = src->colorSpace;
}
@@ -1214,7 +1239,6 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::copy(const QRect& r) const
} else
memcpy(image.bits(), bits(), d->nbytes);
image.d->colortable = d->colortable;
- image.d->offset = d->offset;
image.d->has_alpha_clut = d->has_alpha_clut;
copyMetadata(image.d, d);
return image;
@@ -1303,7 +1327,6 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::copy(const QRect& r) const
}
copyMetadata(image.d, d);
- image.d->offset = offset();
image.d->has_alpha_clut = d->has_alpha_clut;
return image;
}
@@ -1388,7 +1411,6 @@ int QImage::depth() const
}
/*!
- \since 4.6
\fn int QImage::colorCount() const
Returns the size of the color table for the image.
@@ -1652,7 +1674,6 @@ const uchar *QImage::scanLine(int i) const
shared pixel data, because the returned data is const.
\sa scanLine(), constBits()
- \since 4.7
*/
const uchar *QImage::constScanLine(int i) const
{
@@ -1708,7 +1729,6 @@ const uchar *QImage::bits() const
shared pixel data, because the returned data is const.
\sa bits(), constScanLine()
- \since 4.7
*/
const uchar *QImage::constBits() const
{
@@ -1815,7 +1835,6 @@ void QImage::fill(uint pixel)
/*!
\fn void QImage::fill(Qt::GlobalColor color)
\overload
- \since 4.8
Fills the image with the given \a color, described as a standard global
color.
@@ -1841,8 +1860,6 @@ void QImage::fill(Qt::GlobalColor color)
If the depth of the image is 8, the image will be filled with the
index corresponding the \a color in the color table if present; it
will otherwise be filled with 0.
-
- \since 4.8
*/
void QImage::fill(const QColor &color)
@@ -2099,7 +2116,6 @@ void QImage::invertPixels(InvertMode mode)
#endif
/*!
- \since 4.6
Resizes the color table to contain \a colorCount entries.
If the color table is expanded, all the extra colors will be set to
@@ -2203,7 +2219,6 @@ QImage QImage::convertToFormat_helper(Format format, Qt::ImageConversionFlags fl
QIMAGE_SANITYCHECK_MEMORY(image);
- image.d->offset = offset();
copyMetadata(image.d, d);
converter(image.d, d, flags);
@@ -2633,6 +2648,9 @@ void QImage::setPixel(int x, int y, uint index_or_rgb)
case Format_A2RGB30_Premultiplied:
((uint *)s)[x] = qConvertArgb32ToA2rgb30<PixelOrderRGB>(index_or_rgb);
return;
+ case Format_RGBX64:
+ ((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb | 0xff000000);
+ return;
case Format_RGBA64:
case Format_RGBA64_Premultiplied:
((QRgba64 *)s)[x] = QRgba64::fromArgb32(index_or_rgb);
@@ -4581,7 +4599,6 @@ bool QImage::hasAlphaChannel() const
}
/*!
- \since 4.7
Returns the number of bit planes in the image.
The number of bit planes is the number of bits of color and
@@ -4862,14 +4879,24 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
|| (ws * hs) >= (1<<20)
#endif
) {
+ QImage scaledImage;
if (mat.m11() < 0.0F && mat.m22() < 0.0F) { // horizontal/vertical flip
- return smoothScaled(wd, hd).mirrored(true, true).convertToFormat(format());
+ scaledImage = smoothScaled(wd, hd).mirrored(true, true);
} else if (mat.m11() < 0.0F) { // horizontal flip
- return smoothScaled(wd, hd).mirrored(true, false).convertToFormat(format());
+ scaledImage = smoothScaled(wd, hd).mirrored(true, false);
} else if (mat.m22() < 0.0F) { // vertical flip
- return smoothScaled(wd, hd).mirrored(false, true).convertToFormat(format());
+ scaledImage = smoothScaled(wd, hd).mirrored(false, true);
} else { // no flipping
- return smoothScaled(wd, hd).convertToFormat(format());
+ scaledImage = smoothScaled(wd, hd);
+ }
+
+ switch (format()) {
+ case QImage::Format_Mono:
+ case QImage::Format_MonoLSB:
+ case QImage::Format_Indexed8:
+ return scaledImage;
+ default:
+ return scaledImage.convertToFormat(format());
}
}
}
@@ -4911,7 +4938,14 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
if (target_format >= QImage::Format_RGB32) {
// Prevent QPainter from applying devicePixelRatio corrections
- const QImage sImage = (devicePixelRatio() != 1) ? QImage(constBits(), width(), height(), format()) : *this;
+ QImage sImage = (devicePixelRatio() != 1) ? QImage(constBits(), width(), height(), format()) : *this;
+ if (sImage.d != d
+ && (d->format == QImage::Format_MonoLSB
+ || d->format == QImage::Format_Mono
+ || d->format == QImage::Format_Indexed8)) {
+ sImage.d->colortable = d->colortable;
+ sImage.d->has_alpha_clut = d->has_alpha_clut;
+ }
Q_ASSERT(sImage.devicePixelRatio() == 1);
Q_ASSERT(sImage.devicePixelRatio() == dImage.devicePixelRatio());
@@ -4981,6 +5015,9 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
return;
if (d->colorSpace == colorSpace)
return;
+ if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel()))
+ return;
+
detachMetadata(false);
if (d)
d->colorSpace = colorSpace;
@@ -4993,21 +5030,60 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
If the image has no valid color space, the method does nothing.
+ \note If \a colorSpace is not compatible with the current format, the image
+ will be converted to one that is.
+
\sa convertedToColorSpace(), setColorSpace()
*/
void QImage::convertToColorSpace(const QColorSpace &colorSpace)
{
- if (!d)
- return;
- if (!d->colorSpace.isValid())
+ if (!d || !d->colorSpace.isValid())
return;
- if (!colorSpace.isValid()) {
+ if (!colorSpace.isValidTarget()) {
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
return;
}
if (d->colorSpace == colorSpace)
return;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) {
+ *this = convertedToColorSpace(colorSpace);
+ return;
+ }
applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace));
+ if (d->ref.loadRelaxed() != 1)
+ detachMetadata(false);
+ d->colorSpace = colorSpace;
+}
+
+/*!
+ \since 6.8
+
+ Converts the image to \a colorSpace and \a format.
+
+ If the image has no valid color space, the method does nothing,
+ nor if the color space is not compatible with with the format.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \sa convertedToColorSpace(), setColorSpace()
+*/
+void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags)
+{
+ if (!d || !d->colorSpace.isValid())
+ return;
+ if (!colorSpace.isValidTarget()) {
+ qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
+ return;
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format";
+ return;
+ }
+
+ if (d->colorSpace == colorSpace)
+ return convertTo(format, flags);
+ applyColorTransform(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
d->colorSpace = colorSpace;
}
@@ -5018,16 +5094,56 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
If the image has no valid color space, a null QImage is returned.
- \sa convertToColorSpace()
+ \note If \a colorSpace is not compatible with the current format,
+ the returned image will also be converted to a format this is.
+ For more control over returned image format, see the three argument
+ overload of this method.
+
+ \sa convertToColorSpace(), colorTransformed()
*/
QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace) const
{
- if (!d || !d->colorSpace.isValid() || !colorSpace.isValid())
+ if (!d || !d->colorSpace.isValid())
+ return QImage();
+ if (!colorSpace.isValidTarget()) {
+ qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
return QImage();
+ }
if (d->colorSpace == colorSpace)
return *this;
- QImage image = copy();
- image.convertToColorSpace(colorSpace);
+ QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace));
+ image.setColorSpace(colorSpace);
+ return image;
+}
+
+/*!
+ \since 6.8
+
+ Returns the image converted to \a colorSpace and \a format.
+
+ If the image has no valid color space, a null QImage is returned.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \sa colorTransformed()
+*/
+QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags) const
+{
+ if (!d || !d->colorSpace.isValid())
+ return QImage();
+ if (!colorSpace.isValidTarget()) {
+ qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format";
+ return QImage();
+ }
+ if (d->colorSpace == colorSpace)
+ return convertedTo(format, flags);
+ QImage image = colorTransformed(d->colorSpace.transformationToColorSpace(colorSpace), format, flags);
+ image.setColorSpace(colorSpace);
return image;
}
@@ -5052,6 +5168,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
{
if (transform.isIdentity())
return;
+
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) ||
+ !qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel)) {
+ qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format";
+ return;
+ }
+
detach();
if (!d)
return;
@@ -5070,7 +5193,8 @@ void QImage::applyColorTransform(const QColorTransform &transform)
&& oldFormat != QImage::Format_RGBA64_Premultiplied)
convertTo(QImage::Format_RGBA64);
} else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
- && oldFormat != QImage::Format_ARGB32_Premultiplied) {
+ && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888
+ && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
if (hasAlphaChannel())
convertTo(QImage::Format_ARGB32);
else
@@ -5084,7 +5208,10 @@ void QImage::applyColorTransform(const QColorTransform &transform)
case Format_RGBA32FPx4_Premultiplied:
flags = QColorTransformPrivate::Premultiplied;
break;
+ case Format_Grayscale8:
+ case Format_Grayscale16:
case Format_RGB32:
+ case Format_CMYK8888:
case Format_RGBX64:
case Format_RGBX32FPx4:
flags = QColorTransformPrivate::InputOpaque;
@@ -5099,7 +5226,21 @@ void QImage::applyColorTransform(const QColorTransform &transform)
std::function<void(int,int)> transformSegment;
- if (qt_fpColorPrecision(format())) {
+ if (format() == Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ uint8_t *scanline = reinterpret_cast<uint8_t *>(d->data + y * d->bytes_per_line);
+ QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ }
+ };
+ } else if (format() == Format_Grayscale16) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ uint16_t *scanline = reinterpret_cast<uint16_t *>(d->data + y * d->bytes_per_line);
+ QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ }
+ };
+ } else if (qt_fpColorPrecision(format())) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
QRgbaFloat32 *scanline = reinterpret_cast<QRgbaFloat32 *>(d->data + y * d->bytes_per_line);
@@ -5113,6 +5254,13 @@ void QImage::applyColorTransform(const QColorTransform &transform)
QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
}
};
+ } else if (oldFormat == QImage::Format_CMYK8888) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ QCmyk32 *scanline = reinterpret_cast<QCmyk32 *>(d->data + y * d->bytes_per_line);
+ QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
+ }
+ };
} else {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
@@ -5147,23 +5295,497 @@ void QImage::applyColorTransform(const QColorTransform &transform)
}
/*!
+ \since 6.8
+
+ Applies the color transformation \a transform to all pixels in the image, and converts the format of the image to \a toFormat.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+*/
+void QImage::applyColorTransform(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags)
+{
+ if (!d)
+ return;
+ if (transform.isIdentity())
+ return convertTo(toFormat, flags);
+
+ *this = colorTransformed(transform, toFormat, flags);
+}
+
+/*!
\since 6.4
Returns the image color transformed using \a transform on all pixels in the image.
+ \note If \a transform has a source color space which is incompatible with the format of this image,
+ returns a null QImage. If \a transform has a target color space which is incompatible with the format
+ of this image, the image will also be converted to a compatible format. For more control about the
+ choice of the target pixel format, see the three argument overload of this method.
+
\sa applyColorTransform()
*/
QImage QImage::colorTransformed(const QColorTransform &transform) const &
{
- if (!d || !d->colorSpace.isValid())
+ if (!d)
return QImage();
if (transform.isIdentity())
return *this;
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ // All model switching transforms are opaque in at least one end.
+ switch (outColorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
+ case QColorSpace::ColorModel::Gray:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
+ case QColorSpace::ColorModel::Cmyk:
+ return colorTransformed(transform, QImage::Format_CMYK8888);
+ case QColorSpace::ColorModel::Undefined:
+ break;
+ }
+ return QImage();
+ }
+
QImage image = copy();
image.applyColorTransform(transform);
return image;
}
+static bool isRgb32Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGB32:
+ case QImage::Format_ARGB32:
+ case QImage::Format_ARGB32_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isRgb64Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGBX64:
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static bool isRgb32fpx4Data(QImage::Format f)
+{
+ switch (f) {
+ case QImage::Format_RGBX32FPx4:
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+/*!
+ \since 6.8
+
+ Returns the image color transformed using \a transform on all pixels in the image, returning an image of format \a toFormat.
+
+ The specified image conversion \a flags control how the image data
+ is handled during the format conversion process.
+
+ \note If \a transform has a source color space which is incompatible with the format of this image,
+ or a target color space that is incompatible with \a toFormat, returns a null QImage.
+
+ \sa applyColorTransform()
+*/
+QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format toFormat, Qt::ImageConversionFlags flags) const &
+{
+ if (!d)
+ return QImage();
+ if (toFormat == QImage::Format_Invalid)
+ toFormat = format();
+ if (transform.isIdentity())
+ return convertedTo(toFormat, flags);
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid output color space for transform";
+ return QImage();
+ }
+
+ QImage fromImage = *this;
+
+ QImage::Format tmpFormat = toFormat;
+ switch (toFormat) {
+ case QImage::Format_RGB32:
+ case QImage::Format_ARGB32:
+ case QImage::Format_ARGB32_Premultiplied:
+ case QImage::Format_RGBX32FPx4:
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ case QImage::Format_RGBX64:
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ case QImage::Format_Grayscale8:
+ case QImage::Format_Grayscale16:
+ case QImage::Format_CMYK8888:
+ // can be output natively
+ break;
+ case QImage::Format_RGB16:
+ case QImage::Format_RGB444:
+ case QImage::Format_RGB555:
+ case QImage::Format_RGB666:
+ case QImage::Format_RGB888:
+ case QImage::Format_BGR888:
+ case QImage::Format_RGBX8888:
+ tmpFormat = QImage::Format_RGB32;
+ break;
+ case QImage::Format_Mono:
+ case QImage::Format_MonoLSB:
+ case QImage::Format_Indexed8:
+ case QImage::Format_ARGB8565_Premultiplied:
+ case QImage::Format_ARGB6666_Premultiplied:
+ case QImage::Format_ARGB8555_Premultiplied:
+ case QImage::Format_ARGB4444_Premultiplied:
+ case QImage::Format_RGBA8888:
+ case QImage::Format_RGBA8888_Premultiplied:
+ tmpFormat = QImage::Format_ARGB32;
+ break;
+ case QImage::Format_BGR30:
+ case QImage::Format_RGB30:
+ tmpFormat = QImage::Format_RGBX64;
+ break;
+ case QImage::Format_A2BGR30_Premultiplied:
+ case QImage::Format_A2RGB30_Premultiplied:
+ tmpFormat = QImage::Format_RGBA64;
+ break;
+ case QImage::Format_RGBX16FPx4:
+ case QImage::Format_RGBA16FPx4:
+ case QImage::Format_RGBA16FPx4_Premultiplied:
+ tmpFormat = QImage::Format_RGBA32FPx4;
+ break;
+ case QImage::Format_Alpha8:
+ return convertedTo(QImage::Format_Alpha8);
+ case QImage::Format_Invalid:
+ case QImage::NImageFormats:
+ Q_UNREACHABLE();
+ break;
+ }
+ QColorSpace::ColorModel inColorData = qt_csColorData(pixelFormat().colorModel());
+ QColorSpace::ColorModel outColorData = qt_csColorData(toPixelFormat(toFormat).colorModel());
+ // Ensure only precision increasing transforms
+ if (inColorData != outColorData) {
+ if (fromImage.format() == QImage::Format_Grayscale8 && outColorData == QColorSpace::ColorModel::Rgb)
+ tmpFormat = QImage::Format_RGB32;
+ else if (tmpFormat == QImage::Format_Grayscale8 && qt_highColorPrecision(fromImage.format()))
+ tmpFormat = QImage::Format_Grayscale16;
+ else if (fromImage.format() == QImage::Format_Grayscale16 && outColorData == QColorSpace::ColorModel::Rgb)
+ tmpFormat = QImage::Format_RGBX64;
+ } else {
+ if (tmpFormat == QImage::Format_Grayscale8 && fromImage.format() == QImage::Format_Grayscale16)
+ tmpFormat = QImage::Format_Grayscale16;
+ else if (qt_fpColorPrecision(fromImage.format()) && !qt_fpColorPrecision(tmpFormat))
+ tmpFormat = QImage::Format_RGBA32FPx4;
+ else if (isRgb32Data(tmpFormat) && qt_highColorPrecision(fromImage.format(), true))
+ tmpFormat = QImage::Format_RGBA64;
+ }
+
+ QImage toImage(size(), tmpFormat);
+ copyMetadata(&toImage, *this);
+
+ std::function<void(int, int)> transformSegment;
+ QColorTransformPrivate::TransformFlags transFlags = QColorTransformPrivate::Unpremultiplied;
+
+ if (inColorData != outColorData) {
+ // Needs color model switching transform
+ if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Rgb) {
+ // Gray -> RGB
+ if (format() == QImage::Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
+ QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Gray && outColorData == QColorSpace::ColorModel::Cmyk) {
+ // Gray -> CMYK
+ if (format() == QImage::Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Gray) {
+ // RGB -> Gray
+ if (tmpFormat == QImage::Format_Grayscale8) {
+ fromImage.convertTo(QImage::Format_RGB32);
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ fromImage.convertTo(QImage::Format_RGBX64);
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Gray) {
+ // CMYK -> Gray
+ if (tmpFormat == QImage::Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Cmyk && outColorData == QColorSpace::ColorModel::Rgb) {
+ // CMYK -> RGB
+ if (isRgb32Data(tmpFormat) ) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else if (isRgb64Data(tmpFormat)) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ } else {
+ Q_ASSERT(isRgb32fpx4Data(tmpFormat));
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ }
+ };
+ }
+ } else if (inColorData == QColorSpace::ColorModel::Rgb && outColorData == QColorSpace::ColorModel::Cmyk) {
+ // RGB -> CMYK
+ if (!fromImage.hasAlphaChannel())
+ transFlags = QColorTransformPrivate::InputOpaque;
+ else if (qPixelLayouts[fromImage.format()].premultiplied)
+ transFlags = QColorTransformPrivate::Premultiplied;
+ if (isRgb32Data(fromImage.format()) ) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (isRgb64Data(fromImage.format())) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else {
+ Q_ASSERT(isRgb32fpx4Data(fromImage.format()));
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
+ } else {
+ // Conversion on same color model
+ if (pixelFormat().colorModel() == QPixelFormat::Indexed) {
+ for (int i = 0; i < d->colortable.size(); ++i)
+ fromImage.d->colortable[i] = transform.map(d->colortable[i]);
+ return fromImage.convertedTo(toFormat, flags);
+ }
+
+ QImage::Format oldFormat = format();
+ if (qt_fpColorPrecision(oldFormat)) {
+ if (oldFormat != QImage::Format_RGBX32FPx4 && oldFormat != QImage::Format_RGBA32FPx4
+ && oldFormat != QImage::Format_RGBA32FPx4_Premultiplied)
+ fromImage.convertTo(QImage::Format_RGBA32FPx4);
+ } else if (qt_highColorPrecision(oldFormat, true)) {
+ if (oldFormat != QImage::Format_RGBX64 && oldFormat != QImage::Format_RGBA64
+ && oldFormat != QImage::Format_RGBA64_Premultiplied && oldFormat != QImage::Format_Grayscale16)
+ fromImage.convertTo(QImage::Format_RGBA64);
+ } else if (oldFormat != QImage::Format_ARGB32 && oldFormat != QImage::Format_RGB32
+ && oldFormat != QImage::Format_ARGB32_Premultiplied && oldFormat != QImage::Format_CMYK8888
+ && oldFormat != QImage::Format_Grayscale8 && oldFormat != QImage::Format_Grayscale16) {
+ if (hasAlphaChannel())
+ fromImage.convertTo(QImage::Format_ARGB32);
+ else
+ fromImage.convertTo(QImage::Format_RGB32);
+ }
+
+ if (!fromImage.hasAlphaChannel())
+ transFlags = QColorTransformPrivate::InputOpaque;
+ else if (qPixelLayouts[fromImage.format()].premultiplied)
+ transFlags = QColorTransformPrivate::Premultiplied;
+
+ if (fromImage.format() == Format_Grayscale8) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ if (tmpFormat == Format_Grayscale8) {
+ quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(tmpFormat == Format_Grayscale16);
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ } else if (fromImage.format() == Format_Grayscale16) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (fromImage.format() == Format_CMYK8888) {
+ Q_ASSERT(tmpFormat == Format_CMYK8888);
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (isRgb32fpx4Data(fromImage.format())) {
+ Q_ASSERT(isRgb32fpx4Data(tmpFormat));
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgbaFloat32 *in_scanline = reinterpret_cast<const QRgbaFloat32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ };
+ } else if (isRgb64Data(fromImage.format())) {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ if (isRgb32fpx4Data(tmpFormat)) {
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(isRgb64Data(tmpFormat));
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ } else {
+ transformSegment = [&](int yStart, int yEnd) {
+ for (int y = yStart; y < yEnd; ++y) {
+ const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
+ if (isRgb32fpx4Data(tmpFormat)) {
+ QRgbaFloat32 *out_scanline = reinterpret_cast<QRgbaFloat32 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else if (isRgb64Data(tmpFormat)) {
+ QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ } else {
+ Q_ASSERT(isRgb32Data(tmpFormat));
+ QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
+ }
+ }
+ };
+ }
+ }
+
+#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
+ int segments = (qsizetype(width()) * height()) >> 16;
+ segments = std::min(segments, height());
+ QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance();
+ if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) {
+ QSemaphore semaphore;
+ int y = 0;
+ for (int i = 0; i < segments; ++i) {
+ int yn = (height() - y) / (segments - i);
+ threadPool->start([&, y, yn]() {
+ transformSegment(y, y + yn);
+ semaphore.release(1);
+ });
+ y += yn;
+ }
+ semaphore.acquire(segments);
+ } else
+#endif
+ transformSegment(0, height());
+
+ if (tmpFormat != toFormat)
+ toImage.convertTo(toFormat);
+
+ return toImage;
+}
+
/*!
\since 6.4
\overload
@@ -5174,12 +5796,48 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const &
*/
QImage QImage::colorTransformed(const QColorTransform &transform) &&
{
- if (!d || !d->colorSpace.isValid())
+ if (!d)
return QImage();
+
+ QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
+ QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
+ return QImage();
+ }
+ if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ // There is currently no inplace conversion of both colorspace and format, so just use the normal version.
+ switch (outColorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
+ case QColorSpace::ColorModel::Gray:
+ return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_Grayscale16 : QImage::Format_Grayscale8);
+ case QColorSpace::ColorModel::Cmyk:
+ return colorTransformed(transform, QImage::Format_CMYK8888);
+ case QColorSpace::ColorModel::Undefined:
+ break;
+ }
+ return QImage();
+ }
+
applyColorTransform(transform);
return std::move(*this);
}
+/*!
+ \since 6.8
+ \overload
+
+ Returns the image color transformed using \a transform on all pixels in the image.
+
+ \sa applyColorTransform()
+*/
+QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags) &&
+{
+ // There is currently no inplace conversion of both colorspace and format, so just use the normal version.
+ return colorTransformed(transform, format, flags);
+}
+
bool QImageData::convertInPlace(QImage::Format newFormat, Qt::ImageConversionFlags flags)
{
if (format == newFormat)
@@ -5704,6 +6362,19 @@ static constexpr QPixelFormat pixelformats[] = {
/*PREMULTIPLIED*/ QPixelFormat::Premultiplied,
/*INTERPRETATION*/ QPixelFormat::FloatingPoint,
/*BYTE ORDER*/ QPixelFormat::CurrentSystemEndian),
+ //QImage::Format_CMYK8888:
+ QPixelFormat(QPixelFormat::CMYK,
+ /*RED*/ 8,
+ /*GREEN*/ 8,
+ /*BLUE*/ 8,
+ /*FOURTH*/ 8,
+ /*FIFTH*/ 0,
+ /*ALPHA*/ 0,
+ /*ALPHA USAGE*/ QPixelFormat::IgnoresAlpha,
+ /*ALPHA POSITION*/ QPixelFormat::AtBeginning,
+ /*PREMULTIPLIED*/ QPixelFormat::NotPremultiplied,
+ /*INTERPRETATION*/ QPixelFormat::UnsignedInteger,
+ /*BYTE ORDER*/ QPixelFormat::CurrentSystemEndian),
};
static_assert(sizeof(pixelformats) / sizeof(*pixelformats) == QImage::NImageFormats);
@@ -5764,8 +6435,7 @@ QMap<QString, QString> qt_getImageText(const QImage &image, const QString &descr
QMap<QString, QString> qt_getImageTextFromDescription(const QString &description)
{
QMap<QString, QString> text;
- const auto pairs = QStringView{description}.split(u"\n\n");
- for (const auto &pair : pairs) {
+ for (const auto &pair : QStringView{description}.tokenize(u"\n\n")) {
int index = pair.indexOf(u':');
if (index >= 0 && pair.indexOf(u' ') < index) {
if (!pair.trimmed().isEmpty())
diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h
index 86c49ac28a..cba50e5e4c 100644
--- a/src/gui/image/qimage.h
+++ b/src/gui/image/qimage.h
@@ -75,6 +75,7 @@ public:
Format_RGBX32FPx4,
Format_RGBA32FPx4,
Format_RGBA32FPx4_Premultiplied,
+ Format_CMYK8888,
#ifndef Q_QDOC
NImageFormats
#endif
@@ -230,13 +231,18 @@ public:
void invertPixels(InvertMode = InvertRgb);
QColorSpace colorSpace() const;
- [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &) const;
- void convertToColorSpace(const QColorSpace &);
- void setColorSpace(const QColorSpace &);
+ [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace) const;
+ [[nodiscard]] QImage convertedToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const;
+ void convertToColorSpace(const QColorSpace &colorSpace);
+ void convertToColorSpace(const QColorSpace &colorSpace, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
+ void setColorSpace(const QColorSpace &colorSpace);
QImage colorTransformed(const QColorTransform &transform) const &;
+ QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) const &;
QImage colorTransformed(const QColorTransform &transform) &&;
+ QImage colorTransformed(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor) &&;
void applyColorTransform(const QColorTransform &transform);
+ void applyColorTransform(const QColorTransform &transform, QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor);
bool load(QIODevice *device, const char *format);
bool load(const QString &fileName, const char *format = nullptr);
diff --git a/src/gui/image/qimage_conversions.cpp b/src/gui/image/qimage_conversions.cpp
index 2e1eb5e5e4..a806954df2 100644
--- a/src/gui/image/qimage_conversions.cpp
+++ b/src/gui/image/qimage_conversions.cpp
@@ -4,6 +4,7 @@
#include <private/qguiapplication_p.h>
#include <private/qcolortransform_p.h>
#include <private/qcolortrclut_p.h>
+#include <private/qcmyk_p.h>
#include <private/qdrawhelper_p.h>
#include <private/qendian_p.h>
#include <private/qpixellayout_p.h>
@@ -165,7 +166,7 @@ void convert_generic(QImageData *dest, const QImageData *src, Qt::ImageConversio
if (srcLayout->hasAlphaChannel && !srcLayout->premultiplied &&
!destLayout->hasAlphaChannel && destLayout->storeFromRGB32) {
// Avoid unnecessary premultiply and unpremultiply when converting from unpremultiplied src format.
- fetch = qPixelLayouts[src->format + 1].fetchToARGB32PM;
+ fetch = qPixelLayouts[qt_toPremultipliedFormat(src->format)].fetchToARGB32PM;
if (dest->format == QImage::Format_RGB32)
store = storeRGB32FromARGB32;
else
@@ -383,7 +384,7 @@ bool convert_generic_inplace(QImageData *data, QImage::Format dst_format, Qt::Im
if (srcLayout->hasAlphaChannel && !srcLayout->premultiplied &&
!destLayout->hasAlphaChannel && destLayout->storeFromRGB32) {
// Avoid unnecessary premultiply and unpremultiply when converting from unpremultiplied src format.
- fetch = qPixelLayouts[data->format + 1].fetchToARGB32PM;
+ fetch = qPixelLayouts[qt_toPremultipliedFormat(data->format)].fetchToARGB32PM;
if (data->format == QImage::Format_RGB32)
store = storeRGB32FromARGB32;
else
@@ -485,9 +486,8 @@ bool convert_generic_inplace_over_rgb64(QImageData *data, QImage::Format dst_for
if (srcLayout->hasAlphaChannel && !srcLayout->premultiplied &&
destLayout->hasAlphaChannel && !destLayout->premultiplied) {
// Avoid unnecessary premultiply and unpremultiply when converting between two unpremultiplied formats.
- // This abuses the fact unpremultiplied formats are always before their premultiplied counterparts.
- fetch = qPixelLayouts[data->format + 1].fetchToRGBA64PM;
- store = qStoreFromRGBA64PM[dst_format + 1];
+ fetch = qPixelLayouts[qt_toPremultipliedFormat(data->format)].fetchToRGBA64PM;
+ store = qStoreFromRGBA64PM[qt_toPremultipliedFormat(dst_format)];
}
auto convertSegment = [=](int yStart, int yEnd) {
@@ -580,9 +580,8 @@ bool convert_generic_inplace_over_rgba32f(QImageData *data, QImage::Format dst_f
if (srcLayout->hasAlphaChannel && !srcLayout->premultiplied &&
destLayout->hasAlphaChannel && !destLayout->premultiplied) {
// Avoid unnecessary premultiply and unpremultiply when converting between two unpremultiplied formats.
- // This abuses the fact unpremultiplied formats are always before their premultiplied counterparts.
- fetch = qFetchToRGBA32F[data->format + 1];
- store = qStoreFromRGBA32F[dst_format + 1];
+ fetch = qFetchToRGBA32F[qt_toPremultipliedFormat(data->format)];
+ store = qStoreFromRGBA32F[qt_toPremultipliedFormat(dst_format)];
}
auto convertSegment = [=](int yStart, int yEnd) {
@@ -1323,11 +1322,11 @@ static void convert_ARGB32_to_RGBA64(QImageData *dest, const QImageData *src, Qt
const uchar *src_data = src->data;
uchar *dest_data = dest->data;
- const FetchAndConvertPixelsFunc64 fetch = qPixelLayouts[src->format + 1].fetchToRGBA64PM;
+ const FetchAndConvertPixelsFunc64 fetch = qPixelLayouts[qt_toPremultipliedFormat(src->format)].fetchToRGBA64PM;
for (int i = 0; i < src->height; ++i) {
fetch(reinterpret_cast<QRgba64 *>(dest_data), src_data, 0, src->width, nullptr, nullptr);
- src_data += src->bytes_per_line;;
+ src_data += src->bytes_per_line;
dest_data += dest->bytes_per_line;
}
}
@@ -1425,7 +1424,7 @@ static void convert_ARGB_to_gray8(QImageData *dest, const QImageData *src, Qt::I
for (int i = 0; i < src->height; ++i) {
const QRgb *src_line = reinterpret_cast<const QRgb *>(src_data);
- tfd->apply(dest_data, src_line, src->width, flags);
+ tfd->applyReturnGray(dest_data, src_line, src->width, flags);
src_data += sbpl;
dest_data += dbpl;
}
@@ -1462,7 +1461,7 @@ static void convert_ARGB_to_gray16(QImageData *dest, const QImageData *src, Qt::
const int len = std::min(src->width - j, BufferSize);
for (int k = 0; k < len; ++k)
tmp_line[k] = QRgba64::fromArgb32(src_line[j + k]);
- tfd->apply(dest_line + j, tmp_line, len, flags);
+ tfd->applyReturnGray(dest_line + j, tmp_line, len, flags);
j += len;
}
src_data += sbpl;
@@ -1499,7 +1498,7 @@ static void convert_RGBA64_to_gray8(QImageData *dest, const QImageData *src, Qt:
int j = 0;
while (j < src->width) {
const int len = std::min(src->width - j, BufferSize);
- tfd->apply(gray_line, src_line + j, len, flags);
+ tfd->applyReturnGray(gray_line, src_line + j, len, flags);
for (int k = 0; k < len; ++k)
dest_line[j + k] = qt_div_257(gray_line[k]);
j += len;
@@ -1534,7 +1533,7 @@ static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt
for (int i = 0; i < src->height; ++i) {
const QRgba64 *src_line = reinterpret_cast<const QRgba64 *>(src_data);
quint16 *dest_line = reinterpret_cast<quint16 *>(dest_data);
- tfd->apply(dest_line, src_line, src->width, flags);
+ tfd->applyReturnGray(dest_line, src_line, src->width, flags);
src_data += sbpl;
dest_data += dbpl;
}
@@ -2456,6 +2455,34 @@ static bool convert_Grayscale8_to_Indexed8_inplace(QImageData *data, Qt::ImageCo
return true;
}
+template <bool SourceIsPremultiplied>
+static void convert_ARGB32_to_CMYK8888(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags)
+{
+ Q_ASSERT(src->format == QImage::Format_RGB32 ||
+ src->format == QImage::Format_ARGB32 ||
+ src->format == QImage::Format_ARGB32_Premultiplied);
+ Q_ASSERT(dest->format == QImage::Format_CMYK8888);
+ Q_ASSERT(src->width == dest->width);
+ Q_ASSERT(src->height == dest->height);
+
+ const uchar *src_data = src->data;
+ uchar *dest_data = dest->data;
+ for (int y = 0; y < src->height; ++y) {
+ const QRgb *srcRgba = reinterpret_cast<const QRgb *>(src_data);
+ uint *destCmyk = reinterpret_cast<uint *>(dest_data);
+
+ for (int x = 0; x < src->width; ++x) {
+ QRgb sourcePixel = srcRgba[x];
+ if constexpr (SourceIsPremultiplied)
+ sourcePixel = qUnpremultiply(sourcePixel);
+
+ destCmyk[x] = QCmyk32::fromRgba(sourcePixel).toUint();
+ }
+
+ src_data += src->bytes_per_line;;
+ dest_data += dest->bytes_per_line;
+ }
+}
// first index source, second dest
Image_Converter qimage_converter_map[QImage::NImageFormats][QImage::NImageFormats] = {};
@@ -2592,6 +2619,11 @@ static void qInitImageConversions()
qimage_converter_map[QImage::Format_RGBX32FPx4][QImage::Format_RGBA32FPx4] = convert_passthrough;
qimage_converter_map[QImage::Format_RGBX32FPx4][QImage::Format_RGBA32FPx4_Premultiplied] = convert_passthrough;
+ qimage_converter_map[QImage::Format_CMYK8888][QImage::Format_CMYK8888] = convert_passthrough;
+ qimage_converter_map[QImage::Format_RGB32][QImage::Format_CMYK8888] = convert_ARGB32_to_CMYK8888<false>;
+ qimage_converter_map[QImage::Format_ARGB32][QImage::Format_CMYK8888] = convert_ARGB32_to_CMYK8888<false>;
+ qimage_converter_map[QImage::Format_ARGB32_Premultiplied][QImage::Format_CMYK8888] = convert_ARGB32_to_CMYK8888<true>;
+
// Inline converters:
qimage_inplace_converter_map[QImage::Format_Indexed8][QImage::Format_Grayscale8] =
convert_Indexed8_to_Grayscale8_inplace;
diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h
index ba22c99ed7..0d42f94253 100644
--- a/src/gui/image/qimage_p.h
+++ b/src/gui/image/qimage_p.h
@@ -21,6 +21,8 @@
#include <QtCore/private/qnumeric_p.h>
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
+#include <QtCore/qttypetraits.h>
+
QT_BEGIN_NAMESPACE
@@ -92,18 +94,18 @@ QImageData::calculateImageParameters(qsizetype width, qsizetype height, qsizetyp
// calculate the size, taking care of overflows
qsizetype bytes_per_line;
- if (mul_overflow(width, depth, &bytes_per_line))
+ if (qMulOverflow(width, depth, &bytes_per_line))
return invalid;
- if (add_overflow(bytes_per_line, qsizetype(31), &bytes_per_line))
+ if (qAddOverflow(bytes_per_line, qsizetype(31), &bytes_per_line))
return invalid;
// bytes per scanline (must be multiple of 4)
bytes_per_line = (bytes_per_line >> 5) << 2; // can't overflow
qsizetype total_size;
- if (mul_overflow(height, bytes_per_line, &total_size))
+ if (qMulOverflow(height, bytes_per_line, &total_size))
return invalid;
qsizetype dummy;
- if (mul_overflow(height, qsizetype(sizeof(uchar *)), &dummy))
+ if (qMulOverflow(height, qsizetype(sizeof(uchar *)), &dummy))
return invalid; // why is this here?
#if 1 || QT_VERSION < QT_VERSION_CHECK(6,0,0) // ### can only fix this if QImage dimensions are not int anymore
// Disallow images where width * depth calculations might overflow
@@ -193,6 +195,9 @@ inline int qt_depthForFormat(QImage::Format format)
case QImage::Format_RGBA32FPx4_Premultiplied:
depth = 128;
break;
+ case QImage::Format_CMYK8888:
+ depth = 32;
+ break;
}
return depth;
}
@@ -246,6 +251,7 @@ inline QImage::Format qt_opaqueVersion(QImage::Format format)
case QImage::Format_RGBX32FPx4:
case QImage::Format_Grayscale8:
case QImage::Format_Grayscale16:
+ case QImage::Format_CMYK8888:
return format;
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
@@ -309,12 +315,84 @@ inline QImage::Format qt_alphaVersion(QImage::Format format)
case QImage::Format_Alpha8:
case QImage::Format_Grayscale8:
case QImage::Format_Invalid:
+ case QImage::Format_CMYK8888:
case QImage::NImageFormats:
break;
}
return QImage::Format_ARGB32_Premultiplied;
}
+// Returns an opaque version that is compatible with format
+inline QImage::Format qt_maybeDataCompatibleOpaqueVersion(QImage::Format format)
+{
+ switch (format) {
+ case QImage::Format_ARGB6666_Premultiplied:
+ return QImage::Format_RGB666;
+ case QImage::Format_ARGB4444_Premultiplied:
+ return QImage::Format_RGB444;
+ case QImage::Format_RGBA8888:
+ case QImage::Format_RGBA8888_Premultiplied:
+ return QImage::Format_RGBX8888;
+ case QImage::Format_A2BGR30_Premultiplied:
+ return QImage::Format_BGR30;
+ case QImage::Format_A2RGB30_Premultiplied:
+ return QImage::Format_RGB30;
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ return QImage::Format_RGBX64;
+ case QImage::Format_RGBA16FPx4:
+ case QImage::Format_RGBA16FPx4_Premultiplied:
+ return QImage::Format_RGBX16FPx4;
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ return QImage::Format_RGBX32FPx4;
+ case QImage::Format_ARGB32_Premultiplied:
+ case QImage::Format_ARGB32:
+ return QImage::Format_RGB32;
+ case QImage::Format_RGB16:
+ case QImage::Format_RGB32:
+ case QImage::Format_RGB444:
+ case QImage::Format_RGB555:
+ case QImage::Format_RGB666:
+ case QImage::Format_RGB888:
+ case QImage::Format_BGR888:
+ case QImage::Format_RGBX8888:
+ case QImage::Format_BGR30:
+ case QImage::Format_RGB30:
+ case QImage::Format_RGBX64:
+ case QImage::Format_RGBX16FPx4:
+ case QImage::Format_RGBX32FPx4:
+ case QImage::Format_Grayscale8:
+ case QImage::Format_Grayscale16:
+ case QImage::Format_CMYK8888:
+ return format; // Already opaque
+ case QImage::Format_Mono:
+ case QImage::Format_MonoLSB:
+ case QImage::Format_Indexed8:
+ case QImage::Format_ARGB8565_Premultiplied:
+ case QImage::Format_ARGB8555_Premultiplied:
+ case QImage::Format_Alpha8:
+ case QImage::Format_Invalid:
+ case QImage::NImageFormats:
+ break;
+ }
+ return format; // No compatible opaque versions
+}
+
+constexpr QImage::Format qt_toUnpremultipliedFormat(QImage::Format format)
+{
+ // Assumes input is already a premultiplied format with an unpremultiplied counterpart
+ // This abuses the fact unpremultiplied formats are always before their premultiplied counterparts.
+ return static_cast<QImage::Format>(qToUnderlying(format) - 1);
+}
+
+constexpr QImage::Format qt_toPremultipliedFormat(QImage::Format format)
+{
+ // Assumes input is already an unpremultiplied format
+ // This abuses the fact unpremultiplied formats are always before their premultiplied counterparts.
+ return static_cast<QImage::Format>(qToUnderlying(format) + 1);
+}
+
inline bool qt_highColorPrecision(QImage::Format format, bool opaque = false)
{
// Formats with higher color precision than ARGB32_Premultiplied.
@@ -359,10 +437,98 @@ inline bool qt_fpColorPrecision(QImage::Format format)
return false;
}
-inline QImage::Format qt_maybeAlphaVersionWithSameDepth(QImage::Format format)
+inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
{
- const QImage::Format toFormat = qt_alphaVersion(format);
- return qt_depthForFormat(format) == qt_depthForFormat(toFormat) ? toFormat : format;
+ switch (format) {
+ case QPixelFormat::ColorModel::RGB:
+ case QPixelFormat::ColorModel::BGR:
+ case QPixelFormat::ColorModel::Indexed:
+ return QColorSpace::ColorModel::Rgb;
+ case QPixelFormat::ColorModel::Alpha:
+ return QColorSpace::ColorModel::Undefined; // No valid colors
+ case QPixelFormat::ColorModel::Grayscale:
+ return QColorSpace::ColorModel::Gray;
+ case QPixelFormat::ColorModel::CMYK:
+ return QColorSpace::ColorModel::Cmyk;
+ default:
+ break;
+ }
+ return QColorSpace::ColorModel::Undefined;
+}
+
+inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
+{
+ QColorSpace::ColorModel dataCs = qt_csColorData(data);
+
+ if (data == QPixelFormat::ColorModel::Alpha)
+ return true; // Alpha data has no colors and can be handled by any color space
+
+ if (cs == QColorSpace::ColorModel::Undefined || dataCs == QColorSpace::ColorModel::Undefined)
+ return false;
+
+ if (dataCs == cs)
+ return true; // Matching color models
+
+ if (dataCs == QColorSpace::ColorModel::Gray)
+ return true; // Can apply any CS with white point to Gray data
+
+ return false;
+}
+
+inline QImage::Format qt_maybeDataCompatibleAlphaVersion(QImage::Format format)
+{
+ switch (format) {
+ case QImage::Format_RGB32:
+ return QImage::Format_ARGB32_Premultiplied;
+ case QImage::Format_RGB666:
+ return QImage::Format_ARGB6666_Premultiplied;
+ case QImage::Format_RGB444:
+ return QImage::Format_ARGB4444_Premultiplied;
+ case QImage::Format_RGBX8888:
+ return QImage::Format_RGBA8888_Premultiplied;
+ case QImage::Format_BGR30:
+ return QImage::Format_A2BGR30_Premultiplied;
+ case QImage::Format_RGB30:
+ return QImage::Format_A2RGB30_Premultiplied;
+ case QImage::Format_RGBX64:
+ return QImage::Format_RGBA64_Premultiplied;
+ case QImage::Format_RGBX16FPx4:
+ return QImage::Format_RGBA16FPx4_Premultiplied;
+ case QImage::Format_RGBX32FPx4:
+ return QImage::Format_RGBA32FPx4_Premultiplied;
+ case QImage::Format_ARGB32:
+ case QImage::Format_ARGB32_Premultiplied:
+ case QImage::Format_ARGB8565_Premultiplied:
+ case QImage::Format_ARGB8555_Premultiplied:
+ case QImage::Format_ARGB6666_Premultiplied:
+ case QImage::Format_ARGB4444_Premultiplied:
+ case QImage::Format_RGBA8888:
+ case QImage::Format_RGBA8888_Premultiplied:
+ case QImage::Format_A2BGR30_Premultiplied:
+ case QImage::Format_A2RGB30_Premultiplied:
+ case QImage::Format_Alpha8:
+ case QImage::Format_RGBA64:
+ case QImage::Format_RGBA64_Premultiplied:
+ case QImage::Format_RGBA16FPx4:
+ case QImage::Format_RGBA16FPx4_Premultiplied:
+ case QImage::Format_RGBA32FPx4:
+ case QImage::Format_RGBA32FPx4_Premultiplied:
+ return format; // Already alpha versions
+ case QImage::Format_Mono:
+ case QImage::Format_MonoLSB:
+ case QImage::Format_Indexed8:
+ case QImage::Format_RGB16:
+ case QImage::Format_RGB555:
+ case QImage::Format_RGB888:
+ case QImage::Format_BGR888:
+ case QImage::Format_Grayscale8:
+ case QImage::Format_Grayscale16:
+ case QImage::Format_CMYK8888:
+ case QImage::Format_Invalid:
+ case QImage::NImageFormats:
+ break;
+ }
+ return format; // No data-compatible alpha version
}
inline QImage::Format qt_opaqueVersionForPainting(QImage::Format format)
diff --git a/src/gui/image/qimageiohandler.cpp b/src/gui/image/qimageiohandler.cpp
index 264b720b5e..1dcfd9a074 100644
--- a/src/gui/image/qimageiohandler.cpp
+++ b/src/gui/image/qimageiohandler.cpp
@@ -124,13 +124,13 @@
variants should return a list of supported variant names
(QList<QByteArray>) in this option.
- \value OptimizedWrite. A handler which supports this option
+ \value OptimizedWrite A handler which supports this option
is expected to turn on optimization flags when writing.
- \value ProgressiveScanWrite. A handler which supports
+ \value ProgressiveScanWrite A handler which supports
this option is expected to write the image as a progressive scan image.
- \value ImageTransformation. A handler which supports this option can read
+ \value ImageTransformation A handler which supports this option can read
the transformation metadata of an image. A handler that supports this option
should not apply the transformation itself.
*/
diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp
index b2a95d70e4..9366e9cbb1 100644
--- a/src/gui/image/qimagereader.cpp
+++ b/src/gui/image/qimagereader.cpp
@@ -529,14 +529,15 @@ bool QImageReaderPrivate::initHandler()
int currentExtension = 0;
QString fileName = file->fileName();
+ bool fileIsOpen;
do {
file->setFileName(fileName + u'.'
+ QLatin1StringView(extensions.at(currentExtension++).constData()));
- file->open(QIODevice::ReadOnly);
- } while (!file->isOpen() && currentExtension < extensions.size());
+ fileIsOpen = file->open(QIODevice::ReadOnly);
+ } while (!fileIsOpen && currentExtension < extensions.size());
- if (!device->isOpen()) {
+ if (!fileIsOpen) {
imageReaderError = QImageReader::FileNotFoundError;
errorString = QImageReader::tr("File not found");
file->setFileName(fileName); // restore the old file name
@@ -558,7 +559,7 @@ bool QImageReaderPrivate::initHandler()
*/
void QImageReaderPrivate::getText()
{
- if (text.isEmpty() && initHandler() && handler->supportsOption(QImageIOHandler::Description))
+ if (text.isEmpty() && q->supportsOption(QImageIOHandler::Description))
text = qt_getImageTextFromDescription(handler->option(QImageIOHandler::Description).toString());
}
@@ -848,10 +849,7 @@ int QImageReader::quality() const
*/
QSize QImageReader::size() const
{
- if (!d->initHandler())
- return QSize();
-
- if (d->handler->supportsOption(QImageIOHandler::Size))
+ if (supportsOption(QImageIOHandler::Size))
return d->handler->option(QImageIOHandler::Size).toSize();
return QSize();
@@ -871,10 +869,7 @@ QSize QImageReader::size() const
*/
QImage::Format QImageReader::imageFormat() const
{
- if (!d->initHandler())
- return QImage::Format_Invalid;
-
- if (d->handler->supportsOption(QImageIOHandler::ImageFormat))
+ if (supportsOption(QImageIOHandler::ImageFormat))
return (QImage::Format)d->handler->option(QImageIOHandler::ImageFormat).toInt();
return QImage::Format_Invalid;
@@ -946,6 +941,10 @@ QRect QImageReader::clipRect() const
support scaling), QImageReader will use QImage::scale() with
Qt::SmoothScaling.
+ If only one dimension is set in \a size, the other one will be
+ computed from the image's \l {size()} {natural size} so as to
+ maintain the aspect ratio.
+
\sa scaledSize(), setClipRect(), setScaledClipRect()
*/
void QImageReader::setScaledSize(const QSize &size)
@@ -996,9 +995,7 @@ QRect QImageReader::scaledClipRect() const
*/
void QImageReader::setBackgroundColor(const QColor &color)
{
- if (!d->initHandler())
- return;
- if (d->handler->supportsOption(QImageIOHandler::BackgroundColor))
+ if (supportsOption(QImageIOHandler::BackgroundColor))
d->handler->setOption(QImageIOHandler::BackgroundColor, color);
}
@@ -1013,9 +1010,7 @@ void QImageReader::setBackgroundColor(const QColor &color)
*/
QColor QImageReader::backgroundColor() const
{
- if (!d->initHandler())
- return QColor();
- if (d->handler->supportsOption(QImageIOHandler::BackgroundColor))
+ if (supportsOption(QImageIOHandler::BackgroundColor))
return qvariant_cast<QColor>(d->handler->option(QImageIOHandler::BackgroundColor));
return QColor();
}
@@ -1030,9 +1025,7 @@ QColor QImageReader::backgroundColor() const
*/
bool QImageReader::supportsAnimation() const
{
- if (!d->initHandler())
- return false;
- if (d->handler->supportsOption(QImageIOHandler::Animation))
+ if (supportsOption(QImageIOHandler::Animation))
return d->handler->option(QImageIOHandler::Animation).toBool();
return false;
}
@@ -1044,10 +1037,7 @@ bool QImageReader::supportsAnimation() const
*/
QByteArray QImageReader::subType() const
{
- if (!d->initHandler())
- return QByteArray();
-
- if (d->handler->supportsOption(QImageIOHandler::SubType))
+ if (supportsOption(QImageIOHandler::SubType))
return d->handler->option(QImageIOHandler::SubType).toByteArray();
return QByteArray();
}
@@ -1059,10 +1049,7 @@ QByteArray QImageReader::subType() const
*/
QList<QByteArray> QImageReader::supportedSubTypes() const
{
- if (!d->initHandler())
- return QList<QByteArray>();
-
- if (d->handler->supportsOption(QImageIOHandler::SupportedSubTypes))
+ if (supportsOption(QImageIOHandler::SupportedSubTypes))
return qvariant_cast<QList<QByteArray> >(d->handler->option(QImageIOHandler::SupportedSubTypes));
return QList<QByteArray>();
}
@@ -1078,7 +1065,7 @@ QList<QByteArray> QImageReader::supportedSubTypes() const
QImageIOHandler::Transformations QImageReader::transformation() const
{
int option = QImageIOHandler::TransformationNone;
- if (d->initHandler() && d->handler->supportsOption(QImageIOHandler::ImageTransformation))
+ if (supportsOption(QImageIOHandler::ImageTransformation))
option = d->handler->option(QImageIOHandler::ImageTransformation).toInt();
return QImageIOHandler::Transformations(option);
}
@@ -1196,20 +1183,39 @@ bool QImageReader::read(QImage *image)
if (!d->initHandler())
return false;
+ QSize scaledSize = d->scaledSize;
+ if ((scaledSize.width() <= 0 && scaledSize.height() > 0) ||
+ (scaledSize.height() <= 0 && scaledSize.width() > 0)) {
+ // if only one dimension is given, let's try to calculate the second one
+ // based on the original image size and maintaining the aspect ratio
+ if (const QSize originalSize = size(); !originalSize.isEmpty()) {
+ if (scaledSize.width() <= 0) {
+ const auto ratio = qreal(scaledSize.height()) / originalSize.height();
+ scaledSize.setWidth(qRound(originalSize.width() * ratio));
+ } else {
+ const auto ratio = qreal(scaledSize.width()) / originalSize.width();
+ scaledSize.setHeight(qRound(originalSize.height() * ratio));
+ }
+ }
+ }
+
+ const bool supportScaledSize = supportsOption(QImageIOHandler::ScaledSize) && scaledSize.isValid();
+ const bool supportClipRect = supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull();
+ const bool supportScaledClipRect = supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull();
+
// set the handler specific options.
- if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) {
- if ((d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull())
- || d->clipRect.isNull()) {
+ if (supportScaledSize) {
+ if (supportClipRect || d->clipRect.isNull()) {
// Only enable the ScaledSize option if there is no clip rect, or
// if the handler also supports ClipRect.
- d->handler->setOption(QImageIOHandler::ScaledSize, d->scaledSize);
+ d->handler->setOption(QImageIOHandler::ScaledSize, scaledSize);
}
}
- if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull())
+ if (supportClipRect)
d->handler->setOption(QImageIOHandler::ClipRect, d->clipRect);
- if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull())
+ if (supportScaledClipRect)
d->handler->setOption(QImageIOHandler::ScaledClipRect, d->scaledClipRect);
- if (d->handler->supportsOption(QImageIOHandler::Quality))
+ if (supportsOption(QImageIOHandler::Quality))
d->handler->setOption(QImageIOHandler::Quality, d->quality);
// read the image
@@ -1230,9 +1236,9 @@ bool QImageReader::read(QImage *image)
// provide default implementations for any unsupported image
// options
- if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) {
- if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) {
- if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) {
+ if (supportClipRect) {
+ if (supportScaledSize) {
+ if (supportScaledClipRect) {
// all features are supported by the handler; nothing to do.
} else {
// the image is already scaled, so apply scaled clipping.
@@ -1240,12 +1246,12 @@ bool QImageReader::read(QImage *image)
*image = image->copy(d->scaledClipRect);
}
} else {
- if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) {
+ if (supportScaledClipRect) {
// supports scaled clipping but not scaling, most
// likely a broken handler.
} else {
- if (d->scaledSize.isValid()) {
- *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ if (scaledSize.isValid()) {
+ *image = image->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
if (d->scaledClipRect.isValid()) {
*image = image->copy(d->scaledClipRect);
@@ -1253,8 +1259,8 @@ bool QImageReader::read(QImage *image)
}
}
} else {
- if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid() && d->clipRect.isNull()) {
- if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) {
+ if (supportScaledSize && d->clipRect.isNull()) {
+ if (supportScaledClipRect) {
// nothing to do (ClipRect is ignored!)
} else {
// provide all workarounds.
@@ -1263,7 +1269,7 @@ bool QImageReader::read(QImage *image)
}
}
} else {
- if (d->handler->supportsOption(QImageIOHandler::ScaledClipRect) && !d->scaledClipRect.isNull()) {
+ if (supportScaledClipRect) {
// this makes no sense; a handler that supports
// ScaledClipRect but not ScaledSize is broken, and we
// can't work around it.
@@ -1271,8 +1277,8 @@ bool QImageReader::read(QImage *image)
// provide all workarounds.
if (d->clipRect.isValid())
*image = image->copy(d->clipRect);
- if (d->scaledSize.isValid())
- *image = image->scaled(d->scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ if (scaledSize.isValid())
+ *image = image->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
if (d->scaledClipRect.isValid())
*image = image->copy(d->scaledClipRect);
}
diff --git a/src/gui/image/qimagereaderwriterhelpers.cpp b/src/gui/image/qimagereaderwriterhelpers.cpp
index 1255cd827a..502b0f95f0 100644
--- a/src/gui/image/qimagereaderwriterhelpers.cpp
+++ b/src/gui/image/qimagereaderwriterhelpers.cpp
@@ -97,12 +97,14 @@ QList<QByteArray> supportedImageFormats(Capability cap)
return formats;
}
+static constexpr QByteArrayView imagePrefix() noexcept { return "image/"; }
+
QList<QByteArray> supportedMimeTypes(Capability cap)
{
QList<QByteArray> mimeTypes;
mimeTypes.reserve(_qt_NumFormats);
for (const auto &fmt : _qt_BuiltInFormats)
- mimeTypes.append(QByteArrayLiteral("image/") + fmt.mimeType);
+ mimeTypes.emplace_back(imagePrefix() + fmt.mimeType);
#ifndef QT_NO_IMAGEFORMATPLUGIN
appendImagePluginMimeTypes(irhLoader(), pluginCapability(cap), &mimeTypes);
@@ -113,11 +115,11 @@ QList<QByteArray> supportedMimeTypes(Capability cap)
return mimeTypes;
}
-QList<QByteArray> imageFormatsForMimeType(const QByteArray &mimeType, Capability cap)
+QList<QByteArray> imageFormatsForMimeType(QByteArrayView mimeType, Capability cap)
{
QList<QByteArray> formats;
- if (mimeType.startsWith("image/")) {
- const QByteArray type = mimeType.mid(sizeof("image/") - 1);
+ if (mimeType.startsWith(imagePrefix())) {
+ const QByteArrayView type = mimeType.mid(imagePrefix().size());
for (const auto &fmt : _qt_BuiltInFormats) {
if (fmt.mimeType == type && !formats.contains(fmt.extension))
formats << fmt.extension;
diff --git a/src/gui/image/qimagereaderwriterhelpers_p.h b/src/gui/image/qimagereaderwriterhelpers_p.h
index 8e999db5b9..930a57b536 100644
--- a/src/gui/image/qimagereaderwriterhelpers_p.h
+++ b/src/gui/image/qimagereaderwriterhelpers_p.h
@@ -94,7 +94,7 @@ enum Capability {
};
QList<QByteArray> supportedImageFormats(Capability cap);
QList<QByteArray> supportedMimeTypes(Capability cap);
-QList<QByteArray> imageFormatsForMimeType(const QByteArray &mimeType, Capability cap);
+QList<QByteArray> imageFormatsForMimeType(QByteArrayView mimeType, Capability cap);
}
diff --git a/src/gui/image/qmovie.cpp b/src/gui/image/qmovie.cpp
index 875d88225f..0d13639d35 100644
--- a/src/gui/image/qmovie.cpp
+++ b/src/gui/image/qmovie.cpp
@@ -53,7 +53,7 @@
Call supportedFormats() for a list of formats that QMovie supports.
- \sa QLabel, QImageReader, {Movie Example}
+ \sa QLabel, QImageReader
*/
/*! \enum QMovie::MovieState
@@ -144,11 +144,11 @@
#include "qrect.h"
#include "qelapsedtimer.h"
#include "qtimer.h"
-#include "qpair.h"
#include "qmap.h"
#include "qlist.h"
#include "qbuffer.h"
#include "qdir.h"
+#include "qloggingcategory.h"
#include "private/qobject_p.h"
#include "private/qproperty_p.h"
@@ -156,6 +156,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
+
class QFrameInfo
{
public:
@@ -306,6 +308,19 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
return QFrameInfo(); // Invalid
}
+ // For an animated image format, the tradition is that QMovie calls read()
+ // until canRead() == false, because the number of frames may not be known
+ // in advance; but if we're abusing a multi-frame format as an animation,
+ // canRead() may remain true, and we need to stop after reading the maximum
+ // number of frames that the image provides.
+ const bool supportsAnimation = reader->supportsOption(QImageIOHandler::Animation);
+ const int stopAtFrame = supportsAnimation ? -1 : frameCount();
+
+ // For an animated image format, QImageIOHandler::nextImageDelay() should
+ // provide the time to wait until showing the next frame; but multi-frame
+ // formats are not expected to provide this value, so use 1000 ms by default.
+ const auto nextFrameDelay = [&]() { return supportsAnimation ? reader->nextImageDelay() : 1000; };
+
if (cacheMode == QMovie::CacheNone) {
if (frameNumber != currentFrameNumber+1) {
// Non-sequential frame access
@@ -335,8 +350,12 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
}
}
}
- if (reader->canRead()) {
+ qCDebug(lcImageIo, "CacheNone: read frame %d of %d", frameNumber, stopAtFrame);
+ if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
// reader says we can read. Attempt to actually read image
+ // But if it's a non-animated multi-frame format and we know the frame count, stop there.
+ if (stopAtFrame > 0)
+ reader->jumpToImage(frameNumber);
QImage anImage = reader->read();
if (anImage.isNull()) {
// Reading image failed.
@@ -344,7 +363,7 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
}
if (frameNumber > greatestFrameNumber)
greatestFrameNumber = frameNumber;
- return QFrameInfo(QPixmap::fromImage(std::move(anImage)), reader->nextImageDelay());
+ return QFrameInfo(QPixmap::fromImage(std::move(anImage)), nextFrameDelay());
} else if (frameNumber != 0) {
// We've read all frames now. Return an end marker
haveReadAll = true;
@@ -360,15 +379,19 @@ QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
if (frameNumber > greatestFrameNumber) {
// Frame hasn't been read from file yet. Try to do it
for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) {
- if (reader->canRead()) {
+ qCDebug(lcImageIo, "CacheAll: read frame %d of %d", frameNumber, stopAtFrame);
+ if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
// reader says we can read. Attempt to actually read image
+ // But if it's a non-animated multi-frame format and we know the frame count, stop there.
+ if (stopAtFrame > 0)
+ reader->jumpToImage(frameNumber);
QImage anImage = reader->read();
if (anImage.isNull()) {
// Reading image failed.
return QFrameInfo(); // Invalid
}
greatestFrameNumber = i;
- QFrameInfo info(QPixmap::fromImage(std::move(anImage)), reader->nextImageDelay());
+ QFrameInfo info(QPixmap::fromImage(std::move(anImage)), nextFrameDelay());
// Cache it!
frameMap.insert(i, info);
if (i == frameNumber) {
@@ -426,11 +449,7 @@ bool QMoviePrivate::next()
}
// Image and delay OK, update internal state
currentFrameNumber = nextFrameNumber++;
- QSize scaledSize = reader->scaledSize();
- if (scaledSize.isValid() && (scaledSize != info.pixmap.size()))
- currentPixmap = QPixmap::fromImage( info.pixmap.toImage().scaled(scaledSize) );
- else
- currentPixmap = info.pixmap;
+ currentPixmap = info.pixmap;
if (!speed)
return true;
diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp
index 8b7de7ac23..afef16f867 100644
--- a/src/gui/image/qpixmap.cpp
+++ b/src/gui/image/qpixmap.cpp
@@ -289,7 +289,6 @@ QPixmap QPixmap::copy(const QRect &rect) const
/*!
\fn QPixmap::scroll(int dx, int dy, int x, int y, int width, int height, QRegion *exposed)
- \since 4.6
This convenience function is equivalent to calling QPixmap::scroll(\a dx,
\a dy, QRect(\a x, \a y, \a width, \a height), \a exposed).
@@ -298,8 +297,6 @@ QPixmap QPixmap::copy(const QRect &rect) const
*/
/*!
- \since 4.6
-
Scrolls the area \a rect of this pixmap by (\a dx, \a dy). The exposed
region is left unchanged. You can optionally pass a pointer to an empty
QRegion to get the region that is \a exposed by the scroll operation.
@@ -371,7 +368,6 @@ QPixmap &QPixmap::operator=(const QPixmap &pixmap)
/*!
\fn void QPixmap::swap(QPixmap &other)
- \since 4.8
Swaps pixmap \a other with this pixmap. This operation is very
fast and never fails.
@@ -970,12 +966,7 @@ bool QPixmap::isDetached() const
Passing 0 for \a flags sets all the default options. Returns \c true
if the result is that this pixmap is not null.
- Note: this function was part of Qt 3 support in Qt 4.6 and earlier.
- It has been promoted to official API status in 4.7 to support updating
- the pixmap's image without creating a new QPixmap as fromImage() would.
-
\sa fromImage()
- \since 4.7
*/
bool QPixmap::convertFromImage(const QImage &image, Qt::ImageConversionFlags flags)
{
@@ -1280,8 +1271,9 @@ QPixmap QPixmap::transformed(const QTransform &transform,
QPixmap using the fromImage(). If this is too expensive an
operation, you can use QBitmap::fromImage() instead.
- To convert a QPixmap to and from HICON you can use the QtWinExtras
- functions QtWin::toHICON() and QtWin::fromHICON() respectively.
+ To convert a QPixmap to and from HICON you can use the
+ QImage::toHICON() and QImage::fromHICON() functions respectively
+ (after converting the QPixmap to a QImage, as explained above).
\section1 Pixmap Transformations
diff --git a/src/gui/image/qpixmap_win.cpp b/src/gui/image/qpixmap_win.cpp
index 24af7636ce..fc601bccfc 100644
--- a/src/gui/image/qpixmap_win.cpp
+++ b/src/gui/image/qpixmap_win.cpp
@@ -164,7 +164,7 @@ static QImage copyImageData(const BITMAPINFOHEADER &header, const RGBQUAD *color
Q_ASSERT(DWORD(image.sizeInBytes()) == header.biSizeImage);
memcpy(image.bits(), data, header.biSizeImage);
if (format == QImage::Format_RGB888)
- image = image.rgbSwapped();
+ image = std::move(image).rgbSwapped();
break;
default:
Q_UNREACHABLE();
@@ -305,6 +305,7 @@ HBITMAP qt_imageToWinHBITMAP(const QImage &imageIn, int hbitmapFormat)
return nullptr;
}
if (!pixels) {
+ DeleteObject(bitmap);
qErrnoWarning("%s, did not allocate pixel data", __FUNCTION__);
return nullptr;
}
diff --git a/src/gui/image/qpixmapcache.cpp b/src/gui/image/qpixmapcache.cpp
index 82b7060d6f..45c9743f93 100644
--- a/src/gui/image/qpixmapcache.cpp
+++ b/src/gui/image/qpixmapcache.cpp
@@ -184,7 +184,6 @@ public:
void timerEvent(QTimerEvent *) override;
bool insert(const QString& key, const QPixmap &pixmap, int cost);
QPixmapCache::Key insert(const QPixmap &pixmap, int cost);
- bool replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost);
bool remove(const QString &key);
bool remove(const QPixmapCache::Key &key);
@@ -219,10 +218,15 @@ QT_BEGIN_INCLUDE_NAMESPACE
#include "qpixmapcache.moc"
QT_END_INCLUDE_NAMESPACE
-size_t qHash(const QPixmapCache::Key &k, size_t seed)
+/*!
+ size_t QPixmapCache::qHash(const Key &key, size_t seed = 0);
+ \since 6.6
+
+ Returns the hash value for the \a key, using \a seed to seed the calculation.
+*/
+size_t QPixmapCache::Key::hash(size_t seed) const noexcept
{
- const auto *keyData = QPMCache::get(k);
- return qHash(keyData ? keyData->key : 0, seed);
+ return qHash(this->d ? this->d->key : 0, seed);
}
QPMCache::QPMCache()
@@ -255,25 +259,12 @@ bool QPMCache::flushDetachedPixmaps(bool nt)
{
auto mc = maxCost();
const qsizetype currentTotal = totalCost();
+ const qsizetype oldSize = size();
if (currentTotal)
setMaxCost(nt ? currentTotal * 3 / 4 : currentTotal - 1);
setMaxCost(mc);
ps = totalCost();
-
- bool any = false;
- QHash<QString, QPixmapCache::Key>::iterator it = cacheKeys.begin();
- while (it != cacheKeys.end()) {
- const auto value = it.value();
- if (value.isValid() && !contains(value)) {
- releaseKey(value);
- it = cacheKeys.erase(it);
- any = true;
- } else {
- ++it;
- }
- }
-
- return any;
+ return size() != oldSize;
}
void QPMCache::timerEvent(QTimerEvent *)
@@ -292,17 +283,9 @@ void QPMCache::timerEvent(QTimerEvent *)
QPixmap *QPMCache::object(const QString &key) const
{
- QPixmapCache::Key cacheKey = cacheKeys.value(key);
- if (!cacheKey.d || !cacheKey.d->isValid) {
- const_cast<QPMCache *>(this)->cacheKeys.remove(key);
- return nullptr;
- }
- QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(cacheKey);
- //We didn't find the pixmap in the cache, the key is not valid anymore
- if (!ptr) {
- const_cast<QPMCache *>(this)->cacheKeys.remove(key);
- }
- return ptr;
+ if (const auto it = cacheKeys.find(key); it != cacheKeys.cend())
+ return object(it.value());
+ return nullptr;
}
QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
@@ -317,31 +300,24 @@ QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
{
- QPixmapCache::Key &cacheKey = cacheKeys[key];
//If for the same key we add already a pixmap we should delete it
- if (cacheKey.d)
- QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(cacheKey);
-
- //we create a new key the old one has been removed
- cacheKey = createKey();
+ remove(key);
- bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
- if (success) {
- if (!theid) {
- theid = startTimer(flush_time);
- t = false;
- }
- } else {
- //Insertion failed we released the new allocated key
- cacheKeys.remove(key);
+ // this will create a new key; the old one has been removed
+ auto k = insert(pixmap, cost);
+ if (k.isValid()) {
+ k.d->stringKey = key;
+ cacheKeys[key] = std::move(k);
+ return true;
}
- return success;
+ return false;
}
QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
{
- QPixmapCache::Key cacheKey = createKey();
+ QPixmapCache::Key cacheKey = createKey(); // invalidated by ~QPixmapCacheEntry on failed insert
bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
+ Q_ASSERT(success || !cacheKey.isValid());
if (success) {
if (!theid) {
theid = startTimer(flush_time);
@@ -351,34 +327,10 @@ QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
return cacheKey;
}
-bool QPMCache::replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost)
-{
- Q_ASSERT(key.isValid());
- //If for the same key we had already an entry so we should delete the pixmap and use the new one
- QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
-
- QPixmapCache::Key cacheKey = createKey();
-
- bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
- if (success) {
- if (!theid) {
- theid = startTimer(flush_time);
- t = false;
- }
- const_cast<QPixmapCache::Key&>(key) = cacheKey;
- }
- return success;
-}
-
bool QPMCache::remove(const QString &key)
{
- auto cacheKey = cacheKeys.constFind(key);
- //The key was not in the cache
- if (cacheKey == cacheKeys.constEnd())
- return false;
- const bool result = QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(cacheKey.value());
- cacheKeys.erase(cacheKey);
- return result;
+ const auto cacheKey = cacheKeys.take(key);
+ return cacheKey.isValid() && remove(cacheKey);
}
bool QPMCache::remove(const QPixmapCache::Key &key)
@@ -412,7 +364,11 @@ QPixmapCache::Key QPMCache::createKey()
void QPMCache::releaseKey(const QPixmapCache::Key &key)
{
QPixmapCache::KeyData *keyData = key.d;
- if (!keyData || keyData->key > keyArraySize || keyData->key <= 0)
+ if (!keyData)
+ return;
+ if (!keyData->stringKey.isNull())
+ cacheKeys.remove(keyData->stringKey);
+ if (keyData->key > keyArraySize || keyData->key <= 0)
return;
keyData->key--;
keyArray[keyData->key] = freeKey;
@@ -439,7 +395,6 @@ void QPMCache::clear()
killTimer(theid);
theid = 0;
}
- cacheKeys.clear();
}
QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
@@ -474,7 +429,7 @@ QPixmapCacheEntry::~QPixmapCacheEntry()
bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
{
- if (!qt_pixmapcache_thread_test())
+ if (key.isEmpty() || !qt_pixmapcache_thread_test())
return false;
QPixmap *ptr = pm_cache()->object(key);
if (ptr && pixmap)
@@ -526,7 +481,7 @@ bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
{
- if (!qt_pixmapcache_thread_test())
+ if (key.isEmpty() || !qt_pixmapcache_thread_test())
return false;
return pm_cache()->insert(key, pixmap, cost(pixmap));
}
@@ -553,24 +508,25 @@ QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
return pm_cache()->insert(pixmap, cost(pixmap));
}
+#if QT_DEPRECATED_SINCE(6, 6)
/*!
+ \fn bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
+
+ \deprecated [6.6] Use \c{remove(key); key = insert(pixmap);} instead.
+
Replaces the pixmap associated with the given \a key with the \a pixmap
specified. Returns \c true if the \a pixmap has been correctly inserted into
the cache; otherwise returns \c false.
+ The passed \a key is updated to reference \a pixmap now. Other copies of \a
+ key, if any, still refer to the old pixmap, which is, however, removed from
+ the cache by this function.
+
\sa setCacheLimit(), insert()
\since 4.6
*/
-bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
-{
- if (!qt_pixmapcache_thread_test())
- return false;
- //The key is not valid anymore, a flush happened before probably
- if (!key.d || !key.d->isValid)
- return false;
- return pm_cache()->replace(key, pixmap, cost(pixmap));
-}
+#endif // QT_DEPRECATED_SINCE(6, 6)
/*!
Returns the cache limit (in kilobytes).
@@ -607,7 +563,7 @@ void QPixmapCache::setCacheLimit(int n)
*/
void QPixmapCache::remove(const QString &key)
{
- if (!qt_pixmapcache_thread_test())
+ if (key.isEmpty() || !qt_pixmapcache_thread_test())
return;
pm_cache()->remove(key);
}
diff --git a/src/gui/image/qpixmapcache.h b/src/gui/image/qpixmapcache.h
index 433890c68f..72ee1b797f 100644
--- a/src/gui/image/qpixmapcache.h
+++ b/src/gui/image/qpixmapcache.h
@@ -31,6 +31,10 @@ public:
bool isValid() const noexcept;
private:
+ friend size_t qHash(const QPixmapCache::Key &k, size_t seed = 0) noexcept
+ { return k.hash(seed); }
+ size_t hash(size_t seed) const noexcept;
+
KeyData *d;
friend class QPMCache;
friend class QPixmapCache;
@@ -42,13 +46,30 @@ public:
static bool find(const Key &key, QPixmap *pixmap);
static bool insert(const QString &key, const QPixmap &pixmap);
static Key insert(const QPixmap &pixmap);
+#if QT_DEPRECATED_SINCE(6, 6)
+ QT_DEPRECATED_VERSION_X_6_6("Use remove(key), followed by key = insert(pixmap).")
+ QT_GUI_INLINE_SINCE(6, 6)
static bool replace(const Key &key, const QPixmap &pixmap);
+#endif
static void remove(const QString &key);
static void remove(const Key &key);
static void clear();
};
Q_DECLARE_SHARED(QPixmapCache::Key)
+#if QT_DEPRECATED_SINCE(6, 6)
+#if QT_GUI_INLINE_IMPL_SINCE(6, 6)
+bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
+{
+ if (!key.isValid())
+ return false;
+ remove(key);
+ const_cast<Key&>(key) = insert(pixmap);
+ return key.isValid();
+}
+#endif // QT_GUI_INLINE_IMPL_SINCE(6, 6)
+#endif // QT_DEPRECATED_SINCE(6, 6)
+
QT_END_NAMESPACE
#endif // QPIXMAPCACHE_H
diff --git a/src/gui/image/qpixmapcache_p.h b/src/gui/image/qpixmapcache_p.h
index 6418296f56..43c4d9784c 100644
--- a/src/gui/image/qpixmapcache_p.h
+++ b/src/gui/image/qpixmapcache_p.h
@@ -23,8 +23,6 @@
QT_BEGIN_NAMESPACE
-size_t qHash(const QPixmapCache::Key &k, size_t seed = 0);
-
class QPixmapCache::KeyData
{
public:
@@ -33,6 +31,7 @@ public:
: isValid(other.isValid), key(other.key), ref(1) {}
~KeyData() {}
+ QString stringKey;
bool isValid;
int key;
int ref;
diff --git a/src/gui/image/qplatformpixmap.cpp b/src/gui/image/qplatformpixmap.cpp
index 2e622723ee..a297736095 100644
--- a/src/gui/image/qplatformpixmap.cpp
+++ b/src/gui/image/qplatformpixmap.cpp
@@ -65,10 +65,10 @@ QPlatformPixmap *QPlatformPixmap::createCompatiblePlatformPixmap() const
return d;
}
-static QImage makeBitmapCompliantIfNeeded(QPlatformPixmap *d, const QImage &image, Qt::ImageConversionFlags flags)
+static QImage makeBitmapCompliantIfNeeded(QPlatformPixmap *d, QImage image, Qt::ImageConversionFlags flags)
{
if (d->pixelType() == QPlatformPixmap::BitmapType) {
- QImage img = image.convertToFormat(QImage::Format_MonoLSB, flags);
+ QImage img = std::move(image).convertToFormat(QImage::Format_MonoLSB, flags);
// make sure image.color(0) == Qt::color0 (white)
// and image.color(1) == Qt::color1 (black)
@@ -98,7 +98,7 @@ bool QPlatformPixmap::fromFile(const QString &fileName, const char *format,
QImage image = QImageReader(fileName, format).read();
if (image.isNull())
return false;
- fromImage(makeBitmapCompliantIfNeeded(this, image, flags), flags);
+ fromImage(makeBitmapCompliantIfNeeded(this, std::move(image), flags), flags);
return !isNull();
}
@@ -110,7 +110,7 @@ bool QPlatformPixmap::fromData(const uchar *buf, uint len, const char *format, Q
QImage image = QImageReader(&b, format).read();
if (image.isNull())
return false;
- fromImage(makeBitmapCompliantIfNeeded(this, image, flags), flags);
+ fromImage(makeBitmapCompliantIfNeeded(this, std::move(image), flags), flags);
return !isNull();
}
@@ -132,9 +132,9 @@ QBitmap QPlatformPixmap::mask() const
if (!hasAlphaChannel())
return QBitmap();
- const QImage img = toImage();
+ QImage img = toImage();
bool shouldConvert = (img.format() != QImage::Format_ARGB32 && img.format() != QImage::Format_ARGB32_Premultiplied);
- const QImage image = (shouldConvert ? img.convertToFormat(QImage::Format_ARGB32_Premultiplied) : img);
+ const QImage image = (shouldConvert ? std::move(img).convertToFormat(QImage::Format_ARGB32_Premultiplied) : img);
const int w = image.width();
const int h = image.height();
@@ -160,7 +160,7 @@ QBitmap QPlatformPixmap::mask() const
}
}
- return QBitmap::fromImage(mask);
+ return QBitmap::fromImage(std::move(mask));
}
void QPlatformPixmap::setMask(const QBitmap &mask)
@@ -168,7 +168,7 @@ void QPlatformPixmap::setMask(const QBitmap &mask)
QImage image = toImage();
if (mask.size().isEmpty()) {
if (image.depth() != 1) { // hw: ????
- image = image.convertToFormat(QImage::Format_RGB32);
+ image = std::move(image).convertToFormat(QImage::Format_RGB32);
}
} else {
const int w = image.width();
@@ -188,7 +188,7 @@ void QPlatformPixmap::setMask(const QBitmap &mask)
}
default: {
const QImage imageMask = mask.toImage().convertToFormat(QImage::Format_MonoLSB);
- image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ image = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
for (int y = 0; y < h; ++y) {
const uchar *mscan = imageMask.scanLine(y);
QRgb *tscan = (QRgb *)image.scanLine(y);
diff --git a/src/gui/image/qpnghandler.cpp b/src/gui/image/qpnghandler.cpp
index c9b12c15f8..615a36fa36 100644
--- a/src/gui/image/qpnghandler.cpp
+++ b/src/gui/image/qpnghandler.cpp
@@ -554,10 +554,10 @@ bool QPngHandlerPrivate::readPngHeader()
#endif
png_uint_32 profLen;
png_get_iCCP(png_ptr, info_ptr, &name, &compressionType, &profileData, &profLen);
- colorSpace = QColorSpace::fromIccProfile(QByteArray((const char *)profileData, profLen));
- if (!colorSpace.isValid()) {
- qCDebug(lcImageIo) << "QPngHandler: Failed to parse ICC profile";
- } else {
+ Q_UNUSED(name);
+ Q_UNUSED(compressionType);
+ if (profLen > 0) {
+ colorSpace = QColorSpace::fromIccProfile(QByteArray((const char *)profileData, profLen));
QColorSpacePrivate *csD = QColorSpacePrivate::get(colorSpace);
if (csD->description.isEmpty())
csD->description = QString::fromLatin1((const char *)name);
@@ -926,15 +926,15 @@ bool QPNGImageWriter::writeImage(const QImage& image, int compression_in, const
color_type, 0, 0, 0); // sets #channels
#ifdef PNG_iCCP_SUPPORTED
- if (image.colorSpace().isValid()) {
- QColorSpace cs = image.colorSpace();
- // Support the old gamma making it override transferfunction.
- if (gamma != 0.0 && !qFuzzyCompare(cs.gamma(), 1.0f / gamma))
- cs = cs.withTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
+ QColorSpace cs = image.colorSpace();
+ // Support the old gamma making it override transferfunction (if possible)
+ if (cs.isValid() && gamma != 0.0 && !qFuzzyCompare(cs.gamma(), 1.0f / gamma))
+ cs = cs.withTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
+ QByteArray iccProfile = cs.iccProfile();
+ if (!iccProfile.isEmpty()) {
QByteArray iccProfileName = cs.description().toLatin1();
if (iccProfileName.isEmpty())
iccProfileName = QByteArrayLiteral("Custom");
- QByteArray iccProfile = cs.iccProfile();
png_set_iCCP(png_ptr, info_ptr,
#if PNG_LIBPNG_VER < 10500
iccProfileName.data(), PNG_COMPRESSION_TYPE_BASE, iccProfile.data(),
diff --git a/src/gui/image/qppmhandler.cpp b/src/gui/image/qppmhandler.cpp
index e53db69e66..3a4af46195 100644
--- a/src/gui/image/qppmhandler.cpp
+++ b/src/gui/image/qppmhandler.cpp
@@ -32,7 +32,7 @@ static void discard_pbm_line(QIODevice *d)
} while (res > 0 && buf[res-1] != '\n');
}
-static int read_pbm_int(QIODevice *d, bool *ok)
+static int read_pbm_int(QIODevice *d, bool *ok, int maxDigits = -1)
{
char c;
int val = -1;
@@ -50,6 +50,8 @@ static int read_pbm_int(QIODevice *d, bool *ok)
} else {
hasOverflow = true;
}
+ if (maxDigits > 0 && --maxDigits == 0)
+ break;
continue;
} else {
if (c == '#') // comment
@@ -65,6 +67,8 @@ static int read_pbm_int(QIODevice *d, bool *ok)
discard_pbm_line(d);
else
break;
+ if (maxDigits > 0 && --maxDigits == 0)
+ break;
}
if (val < 0)
*ok = false;
@@ -213,7 +217,7 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
b = 0;
for (int i=0; i<8; i++) {
if (i < bitsLeft)
- b = (b << 1) | (read_pbm_int(device, &ok) & 1);
+ b = (b << 1) | (read_pbm_int(device, &ok, 1) & 1);
else
b = (b << 1) | (0 & 1); // pad it our self if we need to
}
@@ -265,13 +269,12 @@ static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, Q
return true;
}
-static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, const QByteArray &sourceFormat)
+static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, QByteArrayView sourceFormat)
{
QByteArray str;
QImage image = sourceImage;
- QByteArray format = sourceFormat;
+ const QByteArrayView format = sourceFormat.left(3); // ignore RAW part
- format = format.left(3); // ignore RAW part
bool gray = format == "pgm";
if (format == "pbm") {
@@ -341,7 +344,7 @@ static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, const QBy
qsizetype bpl = qsizetype(w) * (gray ? 1 : 3);
uchar *buf = new uchar[bpl];
if (image.format() == QImage::Format_Indexed8) {
- QList<QRgb> color = image.colorTable();
+ const QList<QRgb> color = image.colorTable();
for (uint y=0; y<h; y++) {
const uchar *b = image.constScanLine(y);
uchar *p = buf;
diff --git a/src/gui/itemmodels/qfileinfogatherer.cpp b/src/gui/itemmodels/qfileinfogatherer.cpp
index 0e0a3b11a5..41fb0a0db5 100644
--- a/src/gui/itemmodels/qfileinfogatherer.cpp
+++ b/src/gui/itemmodels/qfileinfogatherer.cpp
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qfileinfogatherer_p.h"
+#include <qcoreapplication.h>
#include <qdebug.h>
-#include <qdiriterator.h>
+#include <qdirlisting.h>
+#include <private/qabstractfileiconprovider_p.h>
#include <private/qfileinfo_p.h>
#ifndef Q_OS_WIN
# include <unistd.h>
@@ -57,11 +59,40 @@ QFileInfoGatherer::QFileInfoGatherer(QObject *parent)
*/
QFileInfoGatherer::~QFileInfoGatherer()
{
- abort.storeRelaxed(true);
+ requestAbort();
+ wait();
+}
+
+bool QFileInfoGatherer::event(QEvent *event)
+{
+ if (event->type() == QEvent::DeferredDelete && isRunning()) {
+ // We have been asked to shut down later but were blocked,
+ // so the owning QFileSystemModel proceeded with its shut-down
+ // and deferred the destruction of the gatherer.
+ // If we are still blocked now, then we have three bad options:
+ // terminate, wait forever (preventing the process from shutting down),
+ // or accept a memory leak.
+ requestAbort();
+ if (!wait(5000)) {
+ // If the application is shutting down, then we terminate.
+ // Otherwise assume that sooner or later the thread will finish,
+ // and we delete it then.
+ if (QCoreApplication::closingDown())
+ terminate();
+ else
+ connect(this, &QThread::finished, this, [this]{ delete this; });
+ return true;
+ }
+ }
+
+ return QThread::event(event);
+}
+
+void QFileInfoGatherer::requestAbort()
+{
+ requestInterruption();
QMutexLocker locker(&mutex);
condition.wakeAll();
- locker.unlock();
- wait();
}
void QFileInfoGatherer::setResolveSymlinks(bool enable)
@@ -114,13 +145,12 @@ void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStr
{
QMutexLocker locker(&mutex);
// See if we already have this dir/file in our queue
- int loc = this->path.lastIndexOf(path);
- while (loc > 0) {
- if (this->files.at(loc) == files) {
+ qsizetype loc = 0;
+ while ((loc = this->path.lastIndexOf(path, loc - 1)) != -1) {
+ if (this->files.at(loc) == files)
return;
- }
- loc = this->path.lastIndexOf(path, loc - 1);
}
+
#if QT_CONFIG(thread)
this->path.push(path);
this->files.push(files);
@@ -220,16 +250,23 @@ bool QFileInfoGatherer::isWatching() const
return result;
}
+/*! \internal
+
+ If \a v is \c false, the QFileSystemWatcher used internally will be deleted
+ and subsequent calls to watchPaths() will do nothing.
+
+ If \a v is \c true, subsequent calls to watchPaths() will add those paths to
+ the filesystem watcher; watchPaths() will initialize a QFileSystemWatcher if
+ one hasn't already been initialized.
+*/
void QFileInfoGatherer::setWatching(bool v)
{
#if QT_CONFIG(filesystemwatcher)
QMutexLocker locker(&mutex);
if (v != m_watching) {
- if (!v) {
- delete m_watcher;
- m_watcher = nullptr;
- }
m_watching = v;
+ if (!m_watching)
+ delete std::exchange(m_watcher, nullptr);
}
#else
Q_UNUSED(v);
@@ -281,10 +318,13 @@ void QFileInfoGatherer::list(const QString &directoryPath)
void QFileInfoGatherer::run()
{
forever {
+ // Disallow termination while we are holding a mutex or can be
+ // woken up cleanly.
+ setTerminationEnabled(false);
QMutexLocker locker(&mutex);
- while (!abort.loadRelaxed() && path.isEmpty())
+ while (!isInterruptionRequested() && path.isEmpty())
condition.wait(&mutex);
- if (abort.loadRelaxed())
+ if (isInterruptionRequested())
return;
const QString thisPath = std::as_const(path).front();
path.pop_front();
@@ -292,6 +332,10 @@ void QFileInfoGatherer::run()
files.pop_front();
locker.unlock();
+ // Some of the system APIs we call when gathering file infomration
+ // might hang (e.g. waiting for network), so we explicitly allow
+ // termination now.
+ setTerminationEnabled(true);
getFileInfos(thisPath, thisList);
}
}
@@ -299,8 +343,12 @@ void QFileInfoGatherer::run()
QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const
{
QExtendedInformation info(fileInfo);
- info.icon = m_iconProvider->icon(fileInfo);
- info.displayType = m_iconProvider->type(fileInfo);
+ if (m_iconProvider) {
+ info.icon = m_iconProvider->icon(fileInfo);
+ info.displayType = m_iconProvider->type(fileInfo);
+ } else {
+ info.displayType = QAbstractFileIconProviderPrivate::getFileType(fileInfo);
+ }
#if QT_CONFIG(filesystemwatcher)
// ### Not ready to listen all modifications by default
static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES");
@@ -340,21 +388,23 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil
#ifdef QT_BUILD_INTERNAL
fetchedRoot.storeRelaxed(true);
#endif
- QFileInfoList infoList;
+ QList<std::pair<QString, QFileInfo>> updatedFiles;
+ auto addToUpdatedFiles = [&updatedFiles](QFileInfo &&fileInfo) {
+ fileInfo.stat();
+ updatedFiles.emplace_back(std::pair{translateDriveName(fileInfo), fileInfo});
+ };
+
if (files.isEmpty()) {
- infoList = QDir::drives();
+ // QDir::drives() calls QFSFileEngine::drives() which creates the QFileInfoList on
+ // the stack and return it, so this list is not shared, so no detaching.
+ QFileInfoList infoList = QDir::drives();
+ updatedFiles.reserve(infoList.size());
+ for (auto rit = infoList.rbegin(), rend = infoList.rend(); rit != rend; ++rit)
+ addToUpdatedFiles(std::move(*rit));
} else {
- infoList.reserve(files.size());
- for (const auto &file : files)
- infoList << QFileInfo(file);
- }
- QList<QPair<QString, QFileInfo>> updatedFiles;
- updatedFiles.reserve(infoList.size());
- for (int i = infoList.size() - 1; i >= 0; --i) {
- QFileInfo driveInfo = infoList.at(i);
- driveInfo.stat();
- QString driveName = translateDriveName(driveInfo);
- updatedFiles.append(QPair<QString,QFileInfo>(driveName, driveInfo));
+ updatedFiles.reserve(files.size());
+ for (auto rit = files.crbegin(), rend = files.crend(); rit != rend; ++rit)
+ addToUpdatedFiles(QFileInfo(*rit));
}
emit updates(path, updatedFiles);
return;
@@ -364,14 +414,16 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil
base.start();
QFileInfo fileInfo;
bool firstTime = true;
- QList<QPair<QString, QFileInfo>> updatedFiles;
+ QList<std::pair<QString, QFileInfo>> updatedFiles;
QStringList filesToCheck = files;
QStringList allFiles;
if (files.isEmpty()) {
- QDirIterator dirIt(path, QDir::AllEntries | QDir::System | QDir::Hidden);
- while (!abort.loadRelaxed() && dirIt.hasNext()) {
- fileInfo = dirIt.nextFileInfo();
+ constexpr auto dirFilters = QDir::AllEntries | QDir::System | QDir::Hidden;
+ for (const auto &dirEntry : QDirListing(path, dirFilters)) {
+ if (isInterruptionRequested())
+ break;
+ fileInfo = dirEntry.fileInfo();
fileInfo.stat();
allFiles.append(fileInfo.fileName());
fetch(fileInfo, base, firstTime, updatedFiles, path);
@@ -381,7 +433,7 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil
emit newListOfFiles(path, allFiles);
QStringList::const_iterator filesIt = filesToCheck.constBegin();
- while (!abort.loadRelaxed() && filesIt != filesToCheck.constEnd()) {
+ while (!isInterruptionRequested() && filesIt != filesToCheck.constEnd()) {
fileInfo.setFile(path + QDir::separator() + *filesIt);
++filesIt;
fileInfo.stat();
@@ -393,9 +445,9 @@ void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &fil
}
void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime,
- QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path)
+ QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path)
{
- updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo));
+ updatedFiles.emplace_back(std::pair(fileInfo.fileName(), fileInfo));
QElapsedTimer current;
current.start();
if ((firstTime && updatedFiles.size() > 100) || base.msecsTo(current) > 1000) {
diff --git a/src/gui/itemmodels/qfileinfogatherer_p.h b/src/gui/itemmodels/qfileinfogatherer_p.h
index e4b2bc889f..3d5f59c22e 100644
--- a/src/gui/itemmodels/qfileinfogatherer_p.h
+++ b/src/gui/itemmodels/qfileinfogatherer_p.h
@@ -24,7 +24,6 @@
#include <qfilesystemwatcher.h>
#endif
#include <qabstractfileiconprovider.h>
-#include <qpair.h>
#include <qstack.h>
#include <qdatetime.h>
#include <qdir.h>
@@ -32,6 +31,8 @@
#include <private/qfilesystemengine_p.h>
+#include <utility>
+
QT_REQUIRE_CONFIG(filesystemmodel);
QT_BEGIN_NAMESPACE
@@ -124,7 +125,7 @@ class Q_GUI_EXPORT QFileInfoGatherer : public QThread
Q_OBJECT
Q_SIGNALS:
- void updates(const QString &directory, const QList<QPair<QString, QFileInfo>> &updates);
+ void updates(const QString &directory, const QList<std::pair<QString, QFileInfo>> &updates);
void newListOfFiles(const QString &directory, const QStringList &listOfFiles) const;
void nameResolved(const QString &fileName, const QString &resolvedName) const;
void directoryLoaded(const QString &path);
@@ -148,6 +149,8 @@ public:
QAbstractFileIconProvider *iconProvider() const;
bool resolveSymlinks() const;
+ void requestAbort();
+
public Q_SLOTS:
void list(const QString &directoryPath);
void fetchExtendedInformation(const QString &path, const QStringList &files);
@@ -159,12 +162,15 @@ private Q_SLOTS:
void driveAdded();
void driveRemoved();
+protected:
+ bool event(QEvent *event) override;
+
private:
void run() override;
// called by run():
void getFileInfos(const QString &path, const QStringList &files);
void fetch(const QFileInfo &info, QElapsedTimer &base, bool &firstTime,
- QList<QPair<QString, QFileInfo>> &updatedFiles, const QString &path);
+ QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path);
private:
void createWatcher();
@@ -175,7 +181,6 @@ private:
QStack<QString> path;
QStack<QStringList> files;
// end protected by mutex
- QAtomicInt abort;
#if QT_CONFIG(filesystemwatcher)
QFileSystemWatcher *m_watcher = nullptr;
diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp
index 31480bb52d..290891322f 100644
--- a/src/gui/itemmodels/qfilesystemmodel.cpp
+++ b/src/gui/itemmodels/qfilesystemmodel.cpp
@@ -31,6 +31,7 @@ using namespace Qt::StringLiterals;
\value FilePathRole
\value FileNameRole
\value FilePermissions
+ \value FileInfoRole The QFileInfo object for the index
*/
/*!
@@ -438,7 +439,7 @@ QFileSystemModelPrivate::QFileSystemNode *QFileSystemModelPrivate::node(const QS
QFileSystemModelPrivate *p = const_cast<QFileSystemModelPrivate*>(this);
node = p->addNode(parent, element,info);
#if QT_CONFIG(filesystemwatcher)
- node->populate(fileInfoGatherer.getInfo(info));
+ node->populate(fileInfoGatherer->getInfo(info));
#endif
} else {
node = parent->children.value(element);
@@ -479,7 +480,7 @@ void QFileSystemModel::timerEvent(QTimerEvent *event)
for (int i = 0; i < d->toFetch.size(); ++i) {
const QFileSystemModelPrivate::QFileSystemNode *node = d->toFetch.at(i).node;
if (!node->hasInformation()) {
- d->fileInfoGatherer.fetchExtendedInformation(d->toFetch.at(i).dir,
+ d->fileInfoGatherer->fetchExtendedInformation(d->toFetch.at(i).dir,
QStringList(d->toFetch.at(i).file));
} else {
// qDebug("yah!, you saved a little gerbil soul");
@@ -650,7 +651,7 @@ void QFileSystemModel::fetchMore(const QModelIndex &parent)
return;
indexNode->populatedChildren = true;
#if QT_CONFIG(filesystemwatcher)
- d->fileInfoGatherer.list(filePath(parent));
+ d->fileInfoGatherer->list(filePath(parent));
#endif
}
@@ -693,7 +694,9 @@ QVariant QFileSystemModel::myComputer(int role) const
return QFileSystemModelPrivate::myComputer();
#if QT_CONFIG(filesystemwatcher)
case Qt::DecorationRole:
- return d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Computer);
+ if (auto *provider = d->fileInfoGatherer->iconProvider())
+ return provider->icon(QAbstractFileIconProvider::Computer);
+ break;
#endif
}
return QVariant();
@@ -728,15 +731,16 @@ QVariant QFileSystemModel::data(const QModelIndex &index, int role) const
return filePath(index);
case FileNameRole:
return d->name(index);
+ case FileInfoRole:
+ return QVariant::fromValue(fileInfo(index));
case Qt::DecorationRole:
if (index.column() == QFileSystemModelPrivate::NameColumn) {
QIcon icon = d->icon(index);
#if QT_CONFIG(filesystemwatcher)
if (icon.isNull()) {
- if (d->node(index)->isDir())
- icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::Folder);
- else
- icon = d->fileInfoGatherer.iconProvider()->icon(QAbstractFileIconProvider::File);
+ using P = QAbstractFileIconProvider;
+ if (auto *provider = d->fileInfoGatherer->iconProvider())
+ icon = provider->icon(d->node(index)->isDir() ? P::Folder: P::File);
}
#endif // filesystemwatcher
return icon;
@@ -816,7 +820,7 @@ QString QFileSystemModelPrivate::name(const QModelIndex &index) const
QFileSystemNode *dirNode = node(index);
if (
#if QT_CONFIG(filesystemwatcher)
- fileInfoGatherer.resolveSymlinks() &&
+ fileInfoGatherer->resolveSymlinks() &&
#endif
!resolvedSymLinks.isEmpty() && dirNode->isSymLink(/* ignoreNtfsSymLinks = */ true)) {
QString fullPath = QDir::fromNativeSeparators(filePath(index));
@@ -901,7 +905,7 @@ bool QFileSystemModel::setData(const QModelIndex &idx, const QVariant &value, in
nodeToRename->fileName = newName;
nodeToRename->parent = parentNode;
#if QT_CONFIG(filesystemwatcher)
- nodeToRename->populate(d->fileInfoGatherer.getInfo(QFileInfo(parentPath, newName)));
+ nodeToRename->populate(d->fileInfoGatherer->getInfo(QFileInfo(parentPath, newName)));
#endif
nodeToRename->isVisible = true;
parentNode->children[newName] = nodeToRename.release();
@@ -997,7 +1001,7 @@ Qt::ItemFlags QFileSystemModel::flags(const QModelIndex &index) const
/*!
\internal
*/
-void QFileSystemModelPrivate::_q_performDelayedSort()
+void QFileSystemModelPrivate::performDelayedSort()
{
Q_Q(QFileSystemModel);
q->sort(sortColumn, sortOrder);
@@ -1254,6 +1258,7 @@ QHash<int, QByteArray> QFileSystemModel::roleNames() const
ret.insert(QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath"));
ret.insert(QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName"));
ret.insert(QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions"));
+ ret.insert(QFileSystemModel::FileInfoRole, QByteArrayLiteral("fileInfo"));
return ret;
}
@@ -1326,7 +1331,7 @@ void QFileSystemModel::setOptions(Options options)
#if QT_CONFIG(filesystemwatcher)
Q_D(QFileSystemModel);
if (changed.testFlag(DontWatchForChanges))
- d->fileInfoGatherer.setWatching(!options.testFlag(DontWatchForChanges));
+ d->fileInfoGatherer->setWatching(!options.testFlag(DontWatchForChanges));
#endif
if (changed.testFlag(DontUseCustomDirectoryIcons)) {
@@ -1347,7 +1352,7 @@ QFileSystemModel::Options QFileSystemModel::options() const
result.setFlag(DontResolveSymlinks, !resolveSymlinks());
#if QT_CONFIG(filesystemwatcher)
Q_D(const QFileSystemModel);
- result.setFlag(DontWatchForChanges, !d->fileInfoGatherer.isWatching());
+ result.setFlag(DontWatchForChanges, !d->fileInfoGatherer->isWatching());
#else
result.setFlag(DontWatchForChanges);
#endif
@@ -1369,7 +1374,7 @@ QString QFileSystemModel::filePath(const QModelIndex &index) const
QFileSystemModelPrivate::QFileSystemNode *dirNode = d->node(index);
if (dirNode->isSymLink()
#if QT_CONFIG(filesystemwatcher)
- && d->fileInfoGatherer.resolveSymlinks()
+ && d->fileInfoGatherer->resolveSymlinks()
#endif
&& d->resolvedSymLinks.contains(fullPath)
&& dirNode->isDir()) {
@@ -1431,7 +1436,7 @@ QModelIndex QFileSystemModel::mkdir(const QModelIndex &parent, const QString &na
Q_ASSERT(parentNode->children.contains(name));
QFileSystemModelPrivate::QFileSystemNode *node = parentNode->children[name];
#if QT_CONFIG(filesystemwatcher)
- node->populate(d->fileInfoGatherer.getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name)));
+ node->populate(d->fileInfoGatherer->getInfo(QFileInfo(dir.absolutePath() + QDir::separator() + name)));
#endif
d->addVisibleFiles(parentNode, QStringList(name));
return d->index(node);
@@ -1499,7 +1504,7 @@ QModelIndex QFileSystemModel::setRootPath(const QString &newPath)
if (!rootPath().isEmpty() && rootPath() != "."_L1) {
//This remove the watcher for the old rootPath
#if QT_CONFIG(filesystemwatcher)
- d->fileInfoGatherer.removePath(rootPath());
+ d->fileInfoGatherer->removePath(rootPath());
#endif
//This line "marks" the node as dirty, so the next fetchMore
//call on the path will ask the gatherer to install a watcher again
@@ -1555,7 +1560,7 @@ void QFileSystemModel::setIconProvider(QAbstractFileIconProvider *provider)
{
Q_D(QFileSystemModel);
#if QT_CONFIG(filesystemwatcher)
- d->fileInfoGatherer.setIconProvider(provider);
+ d->fileInfoGatherer->setIconProvider(provider);
#endif
d->root.updateIcon(provider, QString());
}
@@ -1567,9 +1572,9 @@ QAbstractFileIconProvider *QFileSystemModel::iconProvider() const
{
#if QT_CONFIG(filesystemwatcher)
Q_D(const QFileSystemModel);
- return d->fileInfoGatherer.iconProvider();
+ return d->fileInfoGatherer->iconProvider();
#else
- return 0;
+ return nullptr;
#endif
}
@@ -1623,7 +1628,7 @@ void QFileSystemModel::setResolveSymlinks(bool enable)
{
#if QT_CONFIG(filesystemwatcher)
Q_D(QFileSystemModel);
- d->fileInfoGatherer.setResolveSymlinks(enable);
+ d->fileInfoGatherer->setResolveSymlinks(enable);
#else
Q_UNUSED(enable);
#endif
@@ -1633,7 +1638,7 @@ bool QFileSystemModel::resolveSymlinks() const
{
#if QT_CONFIG(filesystemwatcher)
Q_D(const QFileSystemModel);
- return d->fileInfoGatherer.resolveSymlinks();
+ return d->fileInfoGatherer->resolveSymlinks();
#else
return false;
#endif
@@ -1738,7 +1743,7 @@ bool QFileSystemModel::event(QEvent *event)
#if QT_CONFIG(filesystemwatcher)
Q_D(QFileSystemModel);
if (event->type() == QEvent::LanguageChange) {
- d->root.retranslateStrings(d->fileInfoGatherer.iconProvider(), QString());
+ d->root.retranslateStrings(d->fileInfoGatherer->iconProvider(), QString());
return true;
}
#endif
@@ -1752,7 +1757,7 @@ bool QFileSystemModel::rmdir(const QModelIndex &aindex)
#if QT_CONFIG(filesystemwatcher)
if (success) {
QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
- d->fileInfoGatherer.removePath(path);
+ d->fileInfoGatherer->removePath(path);
}
#endif
return success;
@@ -1764,7 +1769,7 @@ bool QFileSystemModel::rmdir(const QModelIndex &aindex)
Performed quick listing and see if any files have been added or removed,
then fetch more information on visible files.
*/
-void QFileSystemModelPrivate::_q_directoryChanged(const QString &directory, const QStringList &files)
+void QFileSystemModelPrivate::directoryChanged(const QString &directory, const QStringList &files)
{
QFileSystemModelPrivate::QFileSystemNode *parentNode = node(directory, false);
if (parentNode->children.size() == 0)
@@ -1912,8 +1917,8 @@ void QFileSystemModelPrivate::removeVisibleFile(QFileSystemNode *parentNode, int
The thread has received new information about files,
update and emit dataChanged if it has actually changed.
*/
-void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path,
- const QList<QPair<QString, QFileInfo>> &updates)
+void QFileSystemModelPrivate::fileSystemChanged(const QString &path,
+ const QList<std::pair<QString, QFileInfo>> &updates)
{
#if QT_CONFIG(filesystemwatcher)
Q_Q(QFileSystemModel);
@@ -1924,7 +1929,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path,
for (const auto &update : updates) {
QString fileName = update.first;
Q_ASSERT(!fileName.isEmpty());
- QExtendedInformation info = fileInfoGatherer.getInfo(update.second);
+ QExtendedInformation info = fileInfoGatherer->getInfo(update.second);
bool previouslyHere = parentNode->children.contains(fileName);
if (!previouslyHere) {
addNode(parentNode, fileName, info.fileInfo());
@@ -2023,7 +2028,7 @@ void QFileSystemModelPrivate::_q_fileSystemChanged(const QString &path,
/*!
\internal
*/
-void QFileSystemModelPrivate::_q_resolvedName(const QString &fileName, const QString &resolvedName)
+void QFileSystemModelPrivate::resolvedName(const QString &fileName, const QString &resolvedName)
{
resolvedSymLinks[fileName] = resolvedName;
}
@@ -2055,22 +2060,41 @@ QStringList QFileSystemModelPrivate::unwatchPathsAt(const QModelIndex &index)
return false;
};
- const QStringList &watchedFiles = fileInfoGatherer.watchedFiles();
+ const QStringList &watchedFiles = fileInfoGatherer->watchedFiles();
std::copy_if(watchedFiles.cbegin(), watchedFiles.cend(),
std::back_inserter(result), filter);
- const QStringList &watchedDirectories = fileInfoGatherer.watchedDirectories();
+ const QStringList &watchedDirectories = fileInfoGatherer->watchedDirectories();
std::copy_if(watchedDirectories.cbegin(), watchedDirectories.cend(),
std::back_inserter(result), filter);
- fileInfoGatherer.unwatchPaths(result);
+ fileInfoGatherer->unwatchPaths(result);
return result;
}
#endif // filesystemwatcher && Q_OS_WIN
-QFileSystemModelPrivate::QFileSystemModelPrivate() = default;
+QFileSystemModelPrivate::QFileSystemModelPrivate()
+#if QT_CONFIG(filesystemwatcher)
+ : fileInfoGatherer(new QFileInfoGatherer)
+#endif // filesystemwatcher
+{
+}
-QFileSystemModelPrivate::~QFileSystemModelPrivate() = default;
+QFileSystemModelPrivate::~QFileSystemModelPrivate()
+{
+#if QT_CONFIG(filesystemwatcher)
+ fileInfoGatherer->requestAbort();
+ if (!fileInfoGatherer->wait(1000)) {
+ // If the thread hangs, perhaps because the network was disconnected
+ // while the gatherer was stat'ing a remote file, then don't block
+ // shutting down the model (which might block a file dialog and the
+ // main thread). Schedule the gatherer for later deletion; it's
+ // destructor will wait for the thread to finish.
+ auto *rawGatherer = fileInfoGatherer.release();
+ rawGatherer->deleteLater();
+ }
+#endif // filesystemwatcher
+}
/*!
\internal
@@ -2081,18 +2105,20 @@ void QFileSystemModelPrivate::init()
delayedSortTimer.setSingleShot(true);
- qRegisterMetaType<QList<QPair<QString, QFileInfo>>>();
+ qRegisterMetaType<QList<std::pair<QString, QFileInfo>>>();
#if QT_CONFIG(filesystemwatcher)
- q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)),
- q, SLOT(_q_directoryChanged(QString,QStringList)));
- q->connect(&fileInfoGatherer, SIGNAL(updates(QString, QList<QPair<QString, QFileInfo>>)), q,
- SLOT(_q_fileSystemChanged(QString, QList<QPair<QString, QFileInfo>>)));
- q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)),
- q, SLOT(_q_resolvedName(QString,QString)));
- q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)),
- q, SIGNAL(directoryLoaded(QString)));
+ QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::newListOfFiles,
+ this, &QFileSystemModelPrivate::directoryChanged);
+ QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::updates,
+ this, &QFileSystemModelPrivate::fileSystemChanged);
+ QObjectPrivate::connect(fileInfoGatherer.get(), &QFileInfoGatherer::nameResolved,
+ this, &QFileSystemModelPrivate::resolvedName);
+ q->connect(fileInfoGatherer.get(), &QFileInfoGatherer::directoryLoaded,
+ q, &QFileSystemModel::directoryLoaded);
#endif // filesystemwatcher
- q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
+ QObjectPrivate::connect(&delayedSortTimer, &QTimer::timeout,
+ this, &QFileSystemModelPrivate::performDelayedSort,
+ Qt::QueuedConnection);
}
/*!
@@ -2105,8 +2131,14 @@ void QFileSystemModelPrivate::init()
*/
bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) const
{
+ // When the model is set to only show files, then a node representing a dir
+ // should be hidden regardless of bypassFilters.
+ // QTBUG-74471
+ const bool hideDirs = (filters & (QDir::Dirs | QDir::AllDirs)) == 0;
+ const bool shouldHideDirNode = hideDirs && node->isDir();
+
// always accept drives
- if (node->parent == &root || bypassFilters.contains(node))
+ if (node->parent == &root || (!shouldHideDirNode && bypassFilters.contains(node)))
return true;
// If we don't know anything yet don't accept it
@@ -2115,7 +2147,6 @@ bool QFileSystemModelPrivate::filtersAcceptsNode(const QFileSystemNode *node) co
const bool filterPermissions = ((filters & QDir::PermissionMask)
&& (filters & QDir::PermissionMask) != QDir::PermissionMask);
- const bool hideDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
const bool hideFiles = !(filters & QDir::Files);
const bool hideReadable = !(!filterPermissions || (filters & QDir::Readable));
const bool hideWritable = !(!filterPermissions || (filters & QDir::Writable));
diff --git a/src/gui/itemmodels/qfilesystemmodel.h b/src/gui/itemmodels/qfilesystemmodel.h
index d614ec2329..1fd1041f15 100644
--- a/src/gui/itemmodels/qfilesystemmodel.h
+++ b/src/gui/itemmodels/qfilesystemmodel.h
@@ -9,7 +9,6 @@
#include <QtCore/qpair.h>
#include <QtCore/qdir.h>
#include <QtGui/qicon.h>
-#include <QtCore/qdiriterator.h>
QT_REQUIRE_CONFIG(filesystemmodel);
@@ -33,11 +32,13 @@ Q_SIGNALS:
void directoryLoaded(const QString &path);
public:
+ // ### Qt 7: renumber these values to be before Qt::UserRole comment.
enum Roles {
FileIconRole = Qt::DecorationRole,
+ FileInfoRole = Qt::UserRole - 1,
FilePathRole = Qt::UserRole + 1,
FileNameRole = Qt::UserRole + 2,
- FilePermissions = Qt::UserRole + 3
+ FilePermissions = Qt::UserRole + 3,
};
enum Option
@@ -114,7 +115,6 @@ public:
qint64 size(const QModelIndex &index) const;
QString type(const QModelIndex &index) const;
- // ### Qt7 merge the two overloads, with tz QTimeZone::LocalTime
QDateTime lastModified(const QModelIndex &index) const;
QDateTime lastModified(const QModelIndex &index, const QTimeZone &tz) const;
@@ -135,13 +135,6 @@ private:
Q_DECLARE_PRIVATE(QFileSystemModel)
Q_DISABLE_COPY(QFileSystemModel)
- Q_PRIVATE_SLOT(d_func(), void _q_directoryChanged(const QString &directory, const QStringList &list))
- Q_PRIVATE_SLOT(d_func(), void _q_performDelayedSort())
- Q_PRIVATE_SLOT(d_func(),
- void _q_fileSystemChanged(const QString &path,
- const QList<QPair<QString, QFileInfo>> &))
- Q_PRIVATE_SLOT(d_func(), void _q_resolvedName(const QString &fileName, const QString &resolvedName))
-
friend class QFileDialogPrivate;
};
diff --git a/src/gui/itemmodels/qfilesystemmodel_p.h b/src/gui/itemmodels/qfilesystemmodel_p.h
index 9198c2f59a..e01b0d56e6 100644
--- a/src/gui/itemmodels/qfilesystemmodel_p.h
+++ b/src/gui/itemmodels/qfilesystemmodel_p.h
@@ -150,8 +150,12 @@ public:
return visibleChildren.indexOf(childName);
}
void updateIcon(QAbstractFileIconProvider *iconProvider, const QString &path) {
+ if (!iconProvider)
+ return;
+
if (info)
info->icon = iconProvider->icon(QFileInfo(path));
+
for (QFileSystemNode *child : std::as_const(children)) {
//On windows the root (My computer) has no path so we don't want to add a / for nothing (e.g. /C:/)
if (!path.isEmpty()) {
@@ -165,6 +169,9 @@ public:
}
void retranslateStrings(QAbstractFileIconProvider *iconProvider, const QString &path) {
+ if (!iconProvider)
+ return;
+
if (info)
info->displayType = iconProvider->type(QFileInfo(path));
for (QFileSystemNode *child : std::as_const(children)) {
@@ -250,18 +257,18 @@ public:
QString type(const QModelIndex &index) const;
QString time(const QModelIndex &index) const;
- void _q_directoryChanged(const QString &directory, const QStringList &list);
- void _q_performDelayedSort();
- void _q_fileSystemChanged(const QString &path, const QList<QPair<QString, QFileInfo>> &);
- void _q_resolvedName(const QString &fileName, const QString &resolvedName);
+ void directoryChanged(const QString &directory, const QStringList &list);
+ void performDelayedSort();
+ void fileSystemChanged(const QString &path, const QList<std::pair<QString, QFileInfo>> &);
+ void resolvedName(const QString &fileName, const QString &resolvedName);
QDir rootDir;
#if QT_CONFIG(filesystemwatcher)
# ifdef Q_OS_WIN
QStringList unwatchPathsAt(const QModelIndex &);
- void watchPaths(const QStringList &paths) { fileInfoGatherer.watchPaths(paths); }
+ void watchPaths(const QStringList &paths) { fileInfoGatherer->watchPaths(paths); }
# endif // Q_OS_WIN
- QFileInfoGatherer fileInfoGatherer;
+ std::unique_ptr<QFileInfoGatherer> fileInfoGatherer;
#endif // filesystemwatcher
QTimer delayedSortTimer;
QHash<const QFileSystemNode*, bool> bypassFilters;
diff --git a/src/gui/itemmodels/qstandarditemmodel.cpp b/src/gui/itemmodels/qstandarditemmodel.cpp
index 64ffdaa3b7..8b3e381431 100644
--- a/src/gui/itemmodels/qstandarditemmodel.cpp
+++ b/src/gui/itemmodels/qstandarditemmodel.cpp
@@ -19,6 +19,11 @@
QT_BEGIN_NAMESPACE
+// Used internally to store the flags
+namespace {
+constexpr auto DataFlagsRole = Qt::ItemDataRole(Qt::UserRole - 1);
+}
+
static inline QString qStandardItemModelDataListMimeType()
{
return QStringLiteral("application/x-qstandarditemmodeldatalist");
@@ -279,8 +284,7 @@ QMap<int, QVariant> QStandardItemPrivate::itemData() const
{
QMap<int, QVariant> result;
for (const auto &data : values) {
- // Qt::UserRole - 1 is used internally to store the flags
- if (data.role != Qt::UserRole - 1)
+ if (data.role != DataFlagsRole)
result.insert(data.role, data.value);
}
return result;
@@ -868,9 +872,15 @@ QStandardItem *QStandardItem::parent() const
Sets the item's data for the given \a role to the specified \a value.
If you subclass QStandardItem and reimplement this function, your
- reimplementation should call emitDataChanged() if you do not call
- the base implementation of setData(). This will ensure that e.g.
- views using the model are notified of the changes.
+ reimplementation should:
+ \list
+ \li call emitDataChanged() if you do not call the base implementation of
+ setData(). This will ensure that e.g. views using the model are notified
+ of the changes
+ \li call the base implementation for roles you don't handle, otherwise
+ setting flags, e.g. by calling setFlags(), setCheckable(), setEditable()
+ etc., will not work.
+ \endlist
\note The default implementation treats Qt::EditRole and Qt::DisplayRole
as referring to the same data.
@@ -924,6 +934,11 @@ void QStandardItem::clearData()
Returns the item's data for the given \a role, or an invalid
QVariant if there is no data for the role.
+ If you reimplement this function, your reimplementation should call
+ the base implementation for roles you don't handle, otherwise getting
+ flags, e.g. by calling flags(), isCheckable(), isEditable() etc.,
+ will not work.
+
\note The default implementation treats Qt::EditRole and Qt::DisplayRole
as referring to the same data.
*/
@@ -983,7 +998,7 @@ void QStandardItem::emitDataChanged()
*/
void QStandardItem::setFlags(Qt::ItemFlags flags)
{
- setData((int)flags, Qt::UserRole - 1);
+ setData((int)flags, DataFlagsRole);
}
/*!
@@ -998,7 +1013,7 @@ void QStandardItem::setFlags(Qt::ItemFlags flags)
*/
Qt::ItemFlags QStandardItem::flags() const
{
- QVariant v = data(Qt::UserRole - 1);
+ QVariant v = data(DataFlagsRole);
if (!v.isValid())
return (Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable
|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled);
@@ -1858,28 +1873,30 @@ QStandardItem *QStandardItem::takeChild(int row, int column)
if (index != -1) {
QModelIndex changedIdx;
item = d->children.at(index);
- if (item && d->model) {
+ if (item) {
QStandardItemPrivate *const item_d = item->d_func();
- const int savedRows = item_d->rows;
- const int savedCols = item_d->columns;
- const QVector<QStandardItem*> savedChildren = item_d->children;
- if (savedRows > 0) {
- d->model->d_func()->rowsAboutToBeRemoved(item, 0, savedRows - 1);
- item_d->rows = 0;
- item_d->children = QVector<QStandardItem*>(); //slightly faster than clear
- d->model->d_func()->rowsRemoved(item, 0, savedRows);
- }
- if (savedCols > 0) {
- d->model->d_func()->columnsAboutToBeRemoved(item, 0, savedCols - 1);
- item_d->columns = 0;
- if (!item_d->children.isEmpty())
+ if (d->model) {
+ QStandardItemModelPrivate *const model_d = d->model->d_func();
+ const int savedRows = item_d->rows;
+ const int savedCols = item_d->columns;
+ const QVector<QStandardItem*> savedChildren = item_d->children;
+ if (savedRows > 0) {
+ model_d->rowsAboutToBeRemoved(item, 0, savedRows - 1);
+ item_d->rows = 0;
+ item_d->children = QVector<QStandardItem*>(); //slightly faster than clear
+ model_d->rowsRemoved(item, 0, savedRows);
+ }
+ if (savedCols > 0) {
+ model_d->columnsAboutToBeRemoved(item, 0, savedCols - 1);
+ item_d->columns = 0;
item_d->children = QVector<QStandardItem*>(); //slightly faster than clear
- d->model->d_func()->columnsRemoved(item, 0, savedCols);
+ model_d->columnsRemoved(item, 0, savedCols);
+ }
+ item_d->rows = savedRows;
+ item_d->columns = savedCols;
+ item_d->children = savedChildren;
+ changedIdx = d->model->indexFromItem(item);
}
- item_d->rows = savedRows;
- item_d->columns = savedCols;
- item_d->children = savedChildren;
- changedIdx = d->model->indexFromItem(item);
item_d->setParentAndModel(nullptr, nullptr);
}
d->children.replace(index, nullptr);
@@ -3099,13 +3116,13 @@ QStringList QStandardItemModel::mimeTypes() const
*/
QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const
{
- QMimeData *data = QAbstractItemModel::mimeData(indexes);
+ std::unique_ptr<QMimeData> data(QAbstractItemModel::mimeData(indexes));
if (!data)
return nullptr;
const QString format = qStandardItemModelDataListMimeType();
if (!mimeTypes().contains(format))
- return data;
+ return data.release();
QByteArray encoded;
QDataStream stream(&encoded, QIODevice::WriteOnly);
@@ -3157,7 +3174,7 @@ QMimeData *QStandardItemModel::mimeData(const QModelIndexList &indexes) const
}
data->setData(format, encoded);
- return data;
+ return data.release();
}
diff --git a/src/gui/itemmodels/qstandarditemmodel_p.h b/src/gui/itemmodels/qstandarditemmodel_p.h
index f25aa44116..a0c3f8a161 100644
--- a/src/gui/itemmodels/qstandarditemmodel_p.h
+++ b/src/gui/itemmodels/qstandarditemmodel_p.h
@@ -15,6 +15,8 @@
// We mean it.
//
+#include <QtGui/qstandarditemmodel.h>
+
#include <QtGui/private/qtguiglobal_p.h>
#include "private/qabstractitemmodel_p.h"
diff --git a/src/gui/kernel/qaction.cpp b/src/gui/kernel/qaction.cpp
index 0f389789a1..c67cfd53b1 100644
--- a/src/gui/kernel/qaction.cpp
+++ b/src/gui/kernel/qaction.cpp
@@ -171,9 +171,7 @@ QObject *QActionPrivate::menu() const
Once a QAction has been created, it should be added to the relevant
menu and toolbar, then connected to the slot which will perform
- the action. For example:
-
- \snippet ../widgets/mainwindows/application/mainwindow.cpp 19
+ the action.
Actions are added to widgets using QWidget::addAction() or
QGraphicsWidget::addAction(). Note that an action must be added to a
@@ -186,7 +184,7 @@ QObject *QActionPrivate::menu() const
use as menu items.
- \sa QMenu, QToolBar, {Qt Widgets - Application Example}
+ \sa QMenu, QToolBar
*/
/*!
diff --git a/src/gui/kernel/qaction_p.h b/src/gui/kernel/qaction_p.h
index 8aa75fdf89..e79cc26b4d 100644
--- a/src/gui/kernel/qaction_p.h
+++ b/src/gui/kernel/qaction_p.h
@@ -21,6 +21,8 @@
#if QT_CONFIG(shortcut)
# include <QtGui/private/qshortcutmap_p.h>
#endif
+
+#include <QtCore/qpointer.h>
#include "private/qobject_p.h"
QT_REQUIRE_CONFIG(action);
diff --git a/src/gui/kernel/qactiongroup_p.h b/src/gui/kernel/qactiongroup_p.h
index c7a058a6f2..ba939a2b10 100644
--- a/src/gui/kernel/qactiongroup_p.h
+++ b/src/gui/kernel/qactiongroup_p.h
@@ -21,6 +21,8 @@
#if QT_CONFIG(shortcut)
# include <QtGui/private/qshortcutmap_p.h>
#endif
+
+#include <QtCore/qpointer.h>
#include "private/qobject_p.h"
QT_REQUIRE_CONFIG(action);
diff --git a/src/gui/kernel/qcursor.cpp b/src/gui/kernel/qcursor.cpp
index 3f0cabbcb1..8e7747559a 100644
--- a/src/gui/kernel/qcursor.cpp
+++ b/src/gui/kernel/qcursor.cpp
@@ -439,8 +439,7 @@ QCursor::QCursor()
QCursor::QCursor(Qt::CursorShape shape)
: d(nullptr)
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
setShape(shape);
}
@@ -498,8 +497,7 @@ bool operator==(const QCursor &lhs, const QCursor &rhs) noexcept
*/
Qt::CursorShape QCursor::shape() const
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
return d->cshape;
}
@@ -512,8 +510,7 @@ Qt::CursorShape QCursor::shape() const
*/
void QCursor::setShape(Qt::CursorShape shape)
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
QCursorData *c = uint(shape) <= Qt::LastCursor ? qt_cursorTable[shape] : nullptr;
if (!c)
c = qt_cursorTable[0];
@@ -547,8 +544,7 @@ void QCursor::setShape(Qt::CursorShape shape)
*/
QBitmap QCursor::bitmap() const
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
if (d->bm)
return *(d->bm);
return QBitmap();
@@ -574,8 +570,7 @@ QBitmap QCursor::bitmap() const
*/
QBitmap QCursor::mask() const
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
if (d->bmm)
return *(d->bmm);
return QBitmap();
@@ -588,8 +583,7 @@ QBitmap QCursor::mask() const
QPixmap QCursor::pixmap() const
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
return d->pixmap;
}
@@ -600,8 +594,7 @@ QPixmap QCursor::pixmap() const
QPoint QCursor::hotSpot() const
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
return QPoint(d->hx, d->hy);
}
@@ -611,8 +604,7 @@ QPoint QCursor::hotSpot() const
QCursor::QCursor(const QCursor &c)
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
d = c.d;
d->ref.ref();
}
@@ -635,8 +627,7 @@ QCursor::~QCursor()
QCursor &QCursor::operator=(const QCursor &c)
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
if (c.d)
c.d->ref.ref();
if (d && !d->ref.deref())
@@ -707,8 +698,7 @@ void QCursorData::initialize()
QCursorData *QCursorData::setBitmap(const QBitmap &bitmap, const QBitmap &mask, int hotX, int hotY, qreal devicePixelRatio)
{
- if (!QCursorData::initialized)
- QCursorData::initialize();
+ QCursorData::initialize();
if (bitmap.depth() != 1 || mask.depth() != 1 || bitmap.size() != mask.size()) {
qWarning("QCursor: Cannot create bitmap cursor; invalid bitmap(s)");
QCursorData *c = qt_cursorTable[0];
diff --git a/src/gui/kernel/qdnd_p.h b/src/gui/kernel/qdnd_p.h
index c863a63620..b5a2e015f4 100644
--- a/src/gui/kernel/qdnd_p.h
+++ b/src/gui/kernel/qdnd_p.h
@@ -27,6 +27,8 @@
#include "private/qobject_p.h"
#include "QtGui/qbackingstore.h"
+#include <QtCore/qpointer.h>
+
QT_REQUIRE_CONFIG(draganddrop);
QT_BEGIN_NAMESPACE
diff --git a/src/gui/kernel/qdrag.cpp b/src/gui/kernel/qdrag.cpp
index 10bf7f33f0..4e707e4915 100644
--- a/src/gui/kernel/qdrag.cpp
+++ b/src/gui/kernel/qdrag.cpp
@@ -9,6 +9,8 @@
#include <qpoint.h>
#include "qdnd_p.h"
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
/*!
@@ -64,7 +66,7 @@ QT_BEGIN_NAMESPACE
required.
\sa {Drag and Drop}, QClipboard, QMimeData, {Draggable Icons Example},
- {Draggable Text Example}, {Drop Site Example}, {Fridge Magnets Example}
+ {Draggable Text Example}, {Drop Site Example}
*/
/*!
diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp
index 19fdcdc97f..d8c11d72a6 100644
--- a/src/gui/kernel/qevent.cpp
+++ b/src/gui/kernel/qevent.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qevent.h"
+
#include "qcursor.h"
#include "private/qguiapplication_p.h"
#include "private/qinputdevice_p.h"
@@ -9,6 +10,7 @@
#include "qpa/qplatformintegration.h"
#include "private/qevent_p.h"
#include "private/qeventpoint_p.h"
+
#include "qfile.h"
#include "qhashfunctions.h"
#include "qmetaobject.h"
@@ -16,6 +18,7 @@
#include "qevent_p.h"
#include "qmath.h"
#include "qloggingcategory.h"
+#include "qpointer.h"
#if QT_CONFIG(draganddrop)
#include <qpa/qplatformdrag.h>
@@ -4125,6 +4128,10 @@ QDebug operator<<(QDebug dbg, const QEvent *e)
dbg << ", text=" << ke->text();
if (ke->isAutoRepeat())
dbg << ", autorepeat, count=" << ke->count();
+ if (dbg.verbosity() > QDebug::DefaultVerbosity) {
+ dbg << ", nativeScanCode=" << ke->nativeScanCode();
+ dbg << ", nativeVirtualKey=" << ke->nativeVirtualKey();
+ }
dbg << ')';
}
break;
@@ -4471,8 +4478,6 @@ Q_IMPL_EVENT_COMMON(QWindowStateChangeEvent)
*/
/*!
- \deprecated [6.2] Use another constructor.
-
Constructs a QTouchEvent with the given \a eventType, \a device,
\a touchPoints, and current keyboard \a modifiers at the time of the event.
*/
@@ -4752,6 +4757,46 @@ Q_IMPL_EVENT_COMMON(QApplicationStateChangeEvent)
Returns the state of the application.
*/
+/*!
+ \class QChildWindowEvent
+ \inmodule QtGui
+ \since 6.7
+ \brief The QChildWindowEvent class contains event parameters for
+ child window changes.
+
+ \ingroup events
+
+ Child window events are sent to windows when children are
+ added or removed.
+
+ In both cases you can only rely on the child being a QWindow
+ — not any subclass thereof. This is because in the
+ QEvent::ChildWindowAdded case the subclass is not yet fully
+ constructed, and in the QEvent::ChildWindowRemoved case it
+ might have already been destructed.
+*/
+
+/*!
+ Constructs a child window event object of a particular \a type
+ for the \a childWindow.
+
+ \a type can be QEvent::ChildWindowAdded or QEvent::ChildWindowRemoved.
+
+ \sa child()
+*/
+QChildWindowEvent::QChildWindowEvent(Type type, QWindow *childWindow)
+ : QEvent(type), c(childWindow)
+{
+}
+
+Q_IMPL_EVENT_COMMON(QChildWindowEvent)
+
+/*!
+ \fn QWindow *QChildWindowEvent::child() const
+
+ Returns the child window that was added or removed.
+*/
+
QMutableTouchEvent::~QMutableTouchEvent()
= default;
diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h
index 900524d218..a24f0c471c 100644
--- a/src/gui/kernel/qevent.h
+++ b/src/gui/kernel/qevent.h
@@ -14,7 +14,6 @@
#include <QtCore/qiodevice.h>
#include <QtCore/qlist.h>
#include <QtCore/qnamespace.h>
-#include <QtCore/qpointer.h>
#include <QtCore/qstring.h>
#include <QtCore/qurl.h>
#include <QtCore/qvariant.h>
@@ -34,6 +33,7 @@ QT_BEGIN_NAMESPACE
class QFile;
class QAction;
class QMouseEvent;
+template <typename T> class QPointer;
class QPointerEvent;
class QScreen;
#if QT_CONFIG(shortcut)
@@ -71,6 +71,7 @@ protected:
class Q_GUI_EXPORT QPointerEvent : public QInputEvent
{
+ Q_GADGET
Q_DECL_EVENT_COMMON(QPointerEvent)
public:
explicit QPointerEvent(Type type, const QPointingDevice *dev,
@@ -1020,6 +1021,17 @@ private:
Qt::ApplicationState m_applicationState;
};
+class Q_GUI_EXPORT QChildWindowEvent : public QEvent
+{
+ Q_DECL_EVENT_COMMON(QChildWindowEvent)
+public:
+ explicit QChildWindowEvent(Type type, QWindow *childWindow);
+ QWindow *child() const { return c; }
+
+private:
+ QWindow *c;
+};
+
QT_END_NAMESPACE
#endif // QEVENT_H
diff --git a/src/gui/kernel/qeventpoint.cpp b/src/gui/kernel/qeventpoint.cpp
index bbb0baee79..66d4f44ea0 100644
--- a/src/gui/kernel/qeventpoint.cpp
+++ b/src/gui/kernel/qeventpoint.cpp
@@ -536,6 +536,7 @@ void QMutableEventPoint::update(const QEventPoint &other, QEventPoint &target)
setEllipseDiameters(target, other.ellipseDiameters());
setRotation(target, other.rotation());
setVelocity(target, other.velocity());
+ setUniqueId(target, other.uniqueId()); // for TUIO
}
/*! \internal
diff --git a/src/gui/kernel/qeventpoint.h b/src/gui/kernel/qeventpoint.h
index 35b8b1f73d..518faecee2 100644
--- a/src/gui/kernel/qeventpoint.h
+++ b/src/gui/kernel/qeventpoint.h
@@ -20,7 +20,6 @@ class Q_GUI_EXPORT QEventPoint
{
Q_GADGET
Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
- QDOC_PROPERTY(QPointingDevice *device READ device CONSTANT) // qdoc doesn't know const
Q_PROPERTY(const QPointingDevice *device READ device CONSTANT)
Q_PROPERTY(int id READ id CONSTANT)
Q_PROPERTY(QPointingDeviceUniqueId uniqueId READ uniqueId CONSTANT)
diff --git a/src/gui/kernel/qeventpoint_p.h b/src/gui/kernel/qeventpoint_p.h
index f70c285e3e..c339c7e3e1 100644
--- a/src/gui/kernel/qeventpoint_p.h
+++ b/src/gui/kernel/qeventpoint_p.h
@@ -17,7 +17,9 @@
#include <QtGui/private/qtguiglobal_p.h>
#include <QtGui/qevent.h>
+
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
index fe40e05536..5228ac9477 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
@@ -9,6 +9,7 @@
#include <qpa/qplatformintegrationfactory_p.h>
#include "private/qevent_p.h"
#include "private/qeventpoint_p.h"
+#include "private/qiconloader_p.h"
#include "qfont.h"
#include "qpointingdevice.h"
#include <qpa/qplatformfontdatabase.h>
@@ -16,6 +17,7 @@
#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformkeymapper.h>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QFileInfo>
@@ -23,6 +25,7 @@
#include <QtCore/QVariant>
#include <QtCore/private/qcoreapplication_p.h>
#include <QtCore/private/qabstracteventdispatcher_p.h>
+#include <QtCore/private/qminimalflatset_p.h>
#include <QtCore/qmutex.h>
#include <QtCore/private/qthread_p.h>
#include <QtCore/private/qlocking_p.h>
@@ -585,13 +588,10 @@ static QWindowGeometrySpecification windowGeometrySpecification = Q_WINDOW_GEOME
\c none disables them.
\li \c {fontengine=freetype}, uses the FreeType font engine.
- \li \c {fontengine=directwrite}, uses the experimental DirectWrite
- font database and defaults to using the DirectWrite font
+ \li \c {fontengine=gdi}, uses the legacy GDI-based
+ font database and defaults to using the GDI font
engine (which is otherwise only used for some font types
- or font properties.) This affects font selection and aims
- to provide font naming more consistent with other platforms,
- but does not support all font formats, such as Postscript
- Type-1 or Microsoft FNT fonts.
+ or font properties.) (Since Qt 6.8).
\li \c {menus=[native|none]}, controls the use of native menus.
Native menus are implemented using Win32 API and are simpler than
@@ -605,7 +605,8 @@ static QWindowGeometrySpecification windowGeometrySpecification = Q_WINDOW_GEOME
\li \c {nocolorfonts} Turn off DirectWrite Color fonts
(since Qt 5.8).
- \li \c {nodirectwrite} Turn off DirectWrite fonts (since Qt 5.8).
+ \li \c {nodirectwrite} Turn off DirectWrite fonts (since Qt 5.8). This implicitly
+ also selects the GDI font engine.
\li \c {nomousefromtouch} Ignores mouse events synthesized
from touch events by the operating system.
@@ -828,7 +829,7 @@ QWindow *QGuiApplication::modalWindow()
CHECK_QAPP_INSTANCE(nullptr)
if (QGuiApplicationPrivate::self->modalWindowList.isEmpty())
return nullptr;
- return QGuiApplicationPrivate::self->modalWindowList.first();
+ return QGuiApplicationPrivate::self->modalWindowList.constFirst();
}
static void updateBlockedStatusRecursion(QWindow *window, bool shouldBeBlocked)
@@ -1193,14 +1194,26 @@ QWindow *QGuiApplication::topLevelAt(const QPoint &pos)
\li \c xcb is a plugin for the X11 window system, used on some desktop Linux platforms.
\endlist
+ \note Calling this function without a QGuiApplication will return the default
+ platform name, if available. The default platform name is not affected by the
+ \c{-platform} command line option, or the \c QT_QPA_PLATFORM environment variable.
+
For more information about the platform plugins for embedded Linux devices,
see \l{Qt for Embedded Linux}.
*/
QString QGuiApplication::platformName()
{
- return QGuiApplicationPrivate::platform_name ?
- *QGuiApplicationPrivate::platform_name : QString();
+ if (!QGuiApplication::instance()) {
+#ifdef QT_QPA_DEFAULT_PLATFORM_NAME
+ return QStringLiteral(QT_QPA_DEFAULT_PLATFORM_NAME);
+#else
+ return QString();
+#endif
+ } else {
+ return QGuiApplicationPrivate::platform_name ?
+ *QGuiApplicationPrivate::platform_name : QString();
+ }
}
Q_LOGGING_CATEGORY(lcQpaPluginLoading, "qt.qpa.plugin");
@@ -1235,6 +1248,10 @@ static void init_platform(const QString &pluginNamesWithArguments, const QString
QGuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath);
if (Q_UNLIKELY(!QGuiApplicationPrivate::platform_integration)) {
if (availablePlugins.contains(name)) {
+ if (name == QStringLiteral("xcb") && QVersionNumber::compare(QLibraryInfo::version(), QVersionNumber(6, 5, 0)) >= 0) {
+ qCWarning(lcQpaPluginLoading).nospace().noquote()
+ << "From 6.5.0, xcb-cursor0 or libxcb-cursor0 is needed to load the Qt xcb platform plugin.";
+ }
qCInfo(lcQpaPluginLoading).nospace().noquote()
<< "Could not load the Qt platform plugin \"" << name << "\" in \""
<< QDir::toNativeSeparators(platformPluginPath) << "\" even though it was found.";
@@ -1514,7 +1531,7 @@ void QGuiApplicationPrivate::createPlatformIntegration()
init_platform(QLatin1StringView(platformName), platformPluginPath, platformThemeName, argc, argv);
if (const QPlatformTheme *theme = platformTheme())
- QStyleHintsPrivate::get(QGuiApplication::styleHints())->setColorScheme(theme->colorScheme());
+ QStyleHintsPrivate::get(QGuiApplication::styleHints())->updateColorScheme(theme->colorScheme());
if (!icon.isEmpty())
forcedWindowIcon = QDir::isAbsolutePath(icon) ? QIcon(icon) : QIcon::fromTheme(icon);
@@ -1830,7 +1847,7 @@ Qt::KeyboardModifiers QGuiApplication::queryKeyboardModifiers()
{
CHECK_QAPP_INSTANCE(Qt::KeyboardModifiers{})
QPlatformIntegration *pi = QGuiApplicationPrivate::platformIntegration();
- return pi->queryKeyboardModifiers();
+ return pi->keyMapper()->queryKeyboardModifiers();
}
/*!
@@ -1888,9 +1905,10 @@ QFunctionPointer QGuiApplication::platformFunction(const QByteArray &function)
Generally, no user interaction can take place before calling exec().
- To make your application perform idle processing, e.g., executing a special
- function whenever there are no pending events, use a QTimer with 0 timeout.
- More advanced idle processing schemes can be achieved using processEvents().
+ To make your application perform idle processing, e.g., executing a
+ special function whenever there are no pending events, use a QChronoTimer
+ with 0ns timeout. More advanced idle processing schemes can be achieved
+ using processEvents().
We recommend that you connect clean-up code to the
\l{QCoreApplication::}{aboutToQuit()} signal, instead of putting it in your
@@ -1980,7 +1998,8 @@ bool QGuiApplication::notify(QObject *object, QEvent *event)
*/
bool QGuiApplication::event(QEvent *e)
{
- if (e->type() == QEvent::LanguageChange) {
+ switch (e->type()) {
+ case QEvent::LanguageChange:
// if the layout direction was set explicitly, then don't override it here
if (layout_direction == Qt::LayoutDirectionAuto)
setLayoutDirection(layout_direction);
@@ -1988,13 +2007,15 @@ bool QGuiApplication::event(QEvent *e)
if (topLevelWindow->flags() != Qt::Desktop)
postEvent(topLevelWindow, new QEvent(QEvent::LanguageChange));
}
- } else if (e->type() == QEvent::ApplicationFontChange ||
- e->type() == QEvent::ApplicationPaletteChange) {
+ break;
+ case QEvent::ApplicationFontChange:
+ case QEvent::ApplicationPaletteChange:
for (auto *topLevelWindow : QGuiApplication::topLevelWindows()) {
if (topLevelWindow->flags() != Qt::Desktop)
postEvent(topLevelWindow, new QEvent(e->type()));
}
- } else if (e->type() == QEvent::Quit) {
+ break;
+ case QEvent::Quit:
// Close open windows. This is done in order to deliver de-expose
// events while the event loop is still running.
for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) {
@@ -2006,8 +2027,10 @@ bool QGuiApplication::event(QEvent *e)
return true;
}
}
+ break;
+ default:
+ break;
}
-
return QCoreApplication::event(e);
}
@@ -2065,8 +2088,8 @@ void Q_TRACE_INSTRUMENT(qtgui) QGuiApplicationPrivate::processWindowSystemEvent(
case QWindowSystemInterfacePrivate::Leave:
QGuiApplicationPrivate::processLeaveEvent(static_cast<QWindowSystemInterfacePrivate::LeaveEvent *>(e));
break;
- case QWindowSystemInterfacePrivate::ActivatedWindow:
- QGuiApplicationPrivate::processActivatedEvent(static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>(e));
+ case QWindowSystemInterfacePrivate::FocusWindow:
+ QGuiApplicationPrivate::processFocusWindowEvent(static_cast<QWindowSystemInterfacePrivate::FocusWindowEvent *>(e));
break;
case QWindowSystemInterfacePrivate::WindowStateChanged:
QGuiApplicationPrivate::processWindowStateChangedEvent(static_cast<QWindowSystemInterfacePrivate::WindowStateChangedEvent *>(e));
@@ -2234,12 +2257,14 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
qAbs(globalPoint.y() - pressPos.y()) > doubleClickDistance)
mousePressButton = Qt::NoButton;
} else {
+ static unsigned long lastPressTimestamp = 0;
mouse_buttons = e->buttons;
if (mousePress) {
ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()->mouseDoubleClickInterval());
- doubleClick = e->timestamp - persistentEPD->eventPoint.pressTimestamp()
+ doubleClick = e->timestamp - lastPressTimestamp
< doubleClickInterval && button == mousePressButton;
mousePressButton = button;
+ lastPressTimestamp = e ->timestamp;
}
}
@@ -2487,10 +2512,10 @@ void QGuiApplicationPrivate::processLeaveEvent(QWindowSystemInterfacePrivate::Le
QCoreApplication::sendSpontaneousEvent(e->leave.data(), &event);
}
-void QGuiApplicationPrivate::processActivatedEvent(QWindowSystemInterfacePrivate::ActivatedWindowEvent *e)
+void QGuiApplicationPrivate::processFocusWindowEvent(QWindowSystemInterfacePrivate::FocusWindowEvent *e)
{
QWindow *previous = QGuiApplicationPrivate::focus_window;
- QWindow *newFocus = e->activated.data();
+ QWindow *newFocus = e->focused.data();
if (previous == newFocus)
return;
@@ -2578,27 +2603,18 @@ void QGuiApplicationPrivate::processWindowStateChangedEvent(QWindowSystemInterfa
void QGuiApplicationPrivate::processWindowScreenChangedEvent(QWindowSystemInterfacePrivate::WindowScreenChangedEvent *wse)
{
- if (QWindow *window = wse->window.data()) {
- if (window->screen() == wse->screen.data())
- return;
+ QWindow *window = wse->window.data();
+ if (!window)
+ return;
- if (QWindow *topLevelWindow = window->d_func()->topLevelWindow(QWindow::ExcludeTransients)) {
- if (QScreen *screen = wse->screen.data())
- topLevelWindow->d_func()->setTopLevelScreen(screen, false /* recreate */);
- else // Fall back to default behavior, and try to find some appropriate screen
- topLevelWindow->setScreen(nullptr);
- }
+ if (window->screen() == wse->screen.data())
+ return;
- // We may have changed scaling; trigger resize event if needed,
- // except on Windows, where we send resize events during WM_DPICHANGED
- // event handling. FIXME: unify DPI change handling across all platforms.
-#ifndef Q_OS_WIN
- if (window->handle()) {
- QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window));
- processGeometryChangeEvent(&gce);
- }
-#endif
- QWindowPrivate::get(window)->updateDevicePixelRatio();
+ if (QWindow *topLevelWindow = window->d_func()->topLevelWindow(QWindow::ExcludeTransients)) {
+ if (QScreen *screen = wse->screen.data())
+ topLevelWindow->d_func()->setTopLevelScreen(screen, false /* recreate */);
+ else // Fall back to default behavior, and try to find some appropriate screen
+ topLevelWindow->setScreen(nullptr);
}
}
@@ -2622,7 +2638,6 @@ void QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(QWindowSystemInt
void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate::ThemeChangeEvent *tce)
{
- QStyleHintsPrivate::get(QGuiApplication::styleHints())->setColorScheme(colorScheme());
if (self)
self->handleThemeChanged();
@@ -2634,22 +2649,15 @@ void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate::
QGuiApplication::sendSpontaneousEvent(window, &themeChangeEvent);
}
-/*!
- \internal
- \brief QGuiApplicationPrivate::colorScheme
- \return the platform theme's color scheme
- or Qt::ColorScheme::Unknown if a platform theme cannot be established
- */
-Qt::ColorScheme QGuiApplicationPrivate::colorScheme()
-{
- return platformTheme() ? platformTheme()->colorScheme()
- : Qt::ColorScheme::Unknown;
-}
-
void QGuiApplicationPrivate::handleThemeChanged()
{
+ const auto newColorScheme = platformTheme() ? platformTheme()->colorScheme()
+ : Qt::ColorScheme::Unknown;
+ QStyleHintsPrivate::get(QGuiApplication::styleHints())->updateColorScheme(newColorScheme);
+
updatePalette();
+ QIconLoader::instance()->updateSystemTheme();
QAbstractFileIconProviderPrivate::clearIconTypeCache();
if (!(applicationResourceFlags & ApplicationFontExplicitlySet)) {
@@ -2916,17 +2924,17 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
// Send the TouchCancel to all windows with active touches and clean up.
QTouchEvent touchEvent(QEvent::TouchCancel, device, e->modifiers);
touchEvent.setTimestamp(e->timestamp);
- QSet<QWindow *> windowsNeedingCancel;
+ constexpr qsizetype Prealloc = decltype(devPriv->activePoints)::mapped_container_type::PreallocatedSize;
+ QMinimalVarLengthFlatSet<QWindow *, Prealloc> windowsNeedingCancel;
for (auto &epd : devPriv->activePoints.values()) {
if (QWindow *w = QMutableEventPoint::window(epd.eventPoint))
windowsNeedingCancel.insert(w);
}
- for (QSet<QWindow *>::const_iterator winIt = windowsNeedingCancel.constBegin(),
- winItEnd = windowsNeedingCancel.constEnd(); winIt != winItEnd; ++winIt) {
- QGuiApplication::sendSpontaneousEvent(*winIt, &touchEvent);
- }
+ for (QWindow *w : windowsNeedingCancel)
+ QGuiApplication::sendSpontaneousEvent(w, &touchEvent);
+
if (!self->synthesizedMousePoints.isEmpty() && !e->synthetic()) {
for (QHash<QWindow *, SynthesizedMouseData>::const_iterator synthIt = self->synthesizedMousePoints.constBegin(),
synthItEnd = self->synthesizedMousePoints.constEnd(); synthIt != synthItEnd; ++synthIt) {
@@ -3254,6 +3262,16 @@ void QGuiApplicationPrivate::processExposeEvent(QWindowSystemInterfacePrivate::E
const bool wasExposed = p->exposed;
p->exposed = e->isExposed && window->screen();
+ // We expect that the platform plugins send DevicePixelRatioChange events.
+ // As a fail-safe make a final check here to make sure the cached DPR value is
+ // always up to date before sending the expose event.
+ if (e->isExposed && !e->region.isEmpty()) {
+ const bool dprWasChanged = QWindowPrivate::get(window)->updateDevicePixelRatio();
+ if (dprWasChanged)
+ qWarning() << "The cached device pixel ratio value was stale on window expose. "
+ << "Please file a QTBUG which explains how to reproduce.";
+ }
+
// We treat expose events for an already exposed window as paint events
if (wasExposed && p->exposed && shouldSynthesizePaintEvents) {
QPaintEvent paintEvent(e->region);
@@ -3429,6 +3447,15 @@ void QGuiApplicationPrivate::updatePalette()
}
}
+QEvent::Type QGuiApplicationPrivate::contextMenuEventType()
+{
+ switch (QGuiApplication::styleHints()->contextMenuTrigger()) {
+ case Qt::ContextMenuTrigger::Press: return QEvent::MouseButtonPress;
+ case Qt::ContextMenuTrigger::Release: return QEvent::MouseButtonRelease;
+ }
+ return QEvent::None;
+}
+
void QGuiApplicationPrivate::clearPalette()
{
delete app_pal;
@@ -3479,7 +3506,8 @@ bool QGuiApplicationPrivate::setPalette(const QPalette &palette)
*/
QPalette QGuiApplicationPrivate::basePalette() const
{
- return platformTheme() ? *platformTheme()->palette() : Qt::gray;
+ const auto pf = platformTheme();
+ return pf && pf->palette() ? *pf->palette() : Qt::gray;
}
void QGuiApplicationPrivate::handlePaletteChanged(const char *className)
@@ -3646,9 +3674,13 @@ void QGuiApplicationPrivate::notifyWindowIconChanged()
The default is \c true.
- If this property is \c true, the applications quits when the last visible
- \l{Primary and Secondary Windows}{primary window} (i.e. top level window
- with no transient parent) is closed.
+ If this property is \c true, the application will attempt to
+ quit when the last visible \l{Primary and Secondary Windows}{primary window}
+ (i.e. top level window with no transient parent) is closed.
+
+ Note that attempting a quit may not necessarily result in the
+ application quitting, for example if there still are active
+ QEventLoopLocker instances, or the QEvent::Quit event is ignored.
\sa quit(), QWindow::close()
*/
@@ -3704,7 +3736,13 @@ bool QGuiApplicationPrivate::lastWindowClosed() const
bool QGuiApplicationPrivate::canQuitAutomatically()
{
- if (quitOnLastWindowClosed && !lastWindowClosed())
+ // The automatic quit functionality is triggered by
+ // both QEventLoopLocker and maybeLastWindowClosed.
+ // Although the former is a QCoreApplication feature
+ // we don't want to quit the application when there
+ // are open windows, regardless of whether the app
+ // also quits automatically on maybeLastWindowClosed.
+ if (!lastWindowClosed())
return false;
return QCoreApplicationPrivate::canQuitAutomatically();
@@ -3768,6 +3806,8 @@ Qt::ApplicationState QGuiApplication::applicationState()
*/
void QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy policy)
{
+ if (qApp)
+ qWarning("setHighDpiScaleFactorRoundingPolicy must be called before creating the QGuiApplication instance");
QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy = policy;
}
@@ -4358,9 +4398,12 @@ void *QGuiApplication::resolveInterface(const char *name, int revision) const
#if QT_CONFIG(xcb)
QT_NATIVE_INTERFACE_RETURN_IF(QX11Application, platformNativeInterface());
#endif
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
QT_NATIVE_INTERFACE_RETURN_IF(QWaylandApplication, platformNativeInterface());
#endif
+#if defined(Q_OS_VISIONOS)
+ QT_NATIVE_INTERFACE_RETURN_IF(QVisionOSApplication, platformIntegration);
+#endif
return QCoreApplication::resolveInterface(name, revision);
}
diff --git a/src/gui/kernel/qguiapplication.h b/src/gui/kernel/qguiapplication.h
index 14bce88c62..23d7fb3d65 100644
--- a/src/gui/kernel/qguiapplication.h
+++ b/src/gui/kernel/qguiapplication.h
@@ -42,7 +42,7 @@ class Q_GUI_EXPORT QGuiApplication : public QCoreApplication
Q_PROPERTY(QString desktopFileName READ desktopFileName WRITE setDesktopFileName)
Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection
NOTIFY layoutDirectionChanged)
- Q_PROPERTY(QString platformName READ platformName STORED false)
+ Q_PROPERTY(QString platformName READ platformName STORED false CONSTANT)
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed
WRITE setQuitOnLastWindowClosed)
Q_PROPERTY(QScreen *primaryScreen READ primaryScreen NOTIFY primaryScreenChanged STORED false)
diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h
index ec63876058..39c490c581 100644
--- a/src/gui/kernel/qguiapplication_p.h
+++ b/src/gui/kernel/qguiapplication_p.h
@@ -34,6 +34,8 @@
# include "private/qshortcutmap_p.h"
#endif
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
@@ -113,7 +115,8 @@ public:
static void processEnterEvent(QWindowSystemInterfacePrivate::EnterEvent *e);
static void processLeaveEvent(QWindowSystemInterfacePrivate::LeaveEvent *e);
- static void processActivatedEvent(QWindowSystemInterfacePrivate::ActivatedWindowEvent *e);
+ static void processFocusWindowEvent(QWindowSystemInterfacePrivate::FocusWindowEvent *e);
+
static void processWindowStateChangedEvent(QWindowSystemInterfacePrivate::WindowStateChangedEvent *e);
static void processWindowScreenChangedEvent(QWindowSystemInterfacePrivate::WindowScreenChangedEvent *e);
static void processWindowDevicePixelRatioChangedEvent(QWindowSystemInterfacePrivate::WindowDevicePixelRatioChangedEvent *e);
@@ -320,6 +323,8 @@ public:
static void updatePalette();
+ static QEvent::Type contextMenuEventType();
+
protected:
virtual void handleThemeChanged();
@@ -336,8 +341,6 @@ private:
friend class QDragManager;
- static Qt::ColorScheme colorScheme();
-
static QGuiApplicationPrivate *self;
static int m_fakeMouseSourcePointId;
#ifdef Q_OS_WIN
@@ -400,8 +403,6 @@ struct Q_GUI_EXPORT QWindowsApplication
virtual bool isWinTabEnabled() const = 0;
virtual bool setWinTabEnabled(bool enabled) = 0;
- virtual bool isDarkMode() const = 0;
-
virtual DarkModeHandling darkModeHandling() const = 0;
virtual void setDarkModeHandling(DarkModeHandling handling) = 0;
@@ -420,7 +421,7 @@ struct Q_GUI_EXPORT QWindowsApplication
virtual QVariant gpu() const = 0; // internal, used by qtdiag
virtual QVariant gpuList() const = 0;
- virtual void lightSystemPalette(QPalette &pal) const = 0;
+ virtual void populateLightSystemPalette(QPalette &pal) const = 0;
};
#endif // Q_OS_WIN
diff --git a/src/gui/kernel/qguiapplication_platform.h b/src/gui/kernel/qguiapplication_platform.h
index b6182e42a3..d9ff01bf14 100644
--- a/src/gui/kernel/qguiapplication_platform.h
+++ b/src/gui/kernel/qguiapplication_platform.h
@@ -23,7 +23,7 @@ typedef struct _XDisplay Display;
struct xcb_connection_t;
#endif
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
struct wl_display;
struct wl_compositor;
struct wl_seat;
@@ -32,6 +32,22 @@ struct wl_pointer;
struct wl_touch;
#endif
+#if defined(Q_OS_VISIONOS) || defined(Q_QDOC)
+# ifdef __OBJC__
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer_capabilities);
+typedef CP_OBJECT_cp_layer_renderer_capabilities *cp_layer_renderer_capabilities_t;
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer_configuration);
+typedef CP_OBJECT_cp_layer_renderer_configuration *cp_layer_renderer_configuration_t;
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer);
+typedef CP_OBJECT_cp_layer_renderer *cp_layer_renderer_t;
+# else
+typedef struct cp_layer_renderer_capabilities_s *cp_layer_renderer_capabilities_t;
+typedef struct cp_layer_renderer_configuration_s *cp_layer_renderer_configuration_t;
+typedef struct cp_layer_renderer_s *cp_layer_renderer_t;
+# endif
+#endif
+
+
QT_BEGIN_NAMESPACE
namespace QNativeInterface
@@ -46,7 +62,7 @@ struct Q_GUI_EXPORT QX11Application
};
#endif
-#if defined(Q_OS_UNIX) || defined(Q_CLANG_QDOC)
+#if QT_CONFIG(wayland) || defined(Q_QDOC)
struct Q_GUI_EXPORT QWaylandApplication
{
QT_DECLARE_NATIVE_INTERFACE(QWaylandApplication, 1, QGuiApplication)
@@ -61,6 +77,20 @@ struct Q_GUI_EXPORT QWaylandApplication
};
#endif
+#if defined(Q_OS_VISIONOS) || defined(Q_QDOC)
+struct Q_GUI_EXPORT QVisionOSApplication
+{
+ QT_DECLARE_NATIVE_INTERFACE(QVisionOSApplication, 1, QGuiApplication)
+ struct ImmersiveSpaceCompositorLayer {
+ virtual void configure(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t) const {}
+ virtual void render(cp_layer_renderer_t) = 0;
+ };
+ virtual void setImmersiveSpaceCompositorLayer(ImmersiveSpaceCompositorLayer *layer) = 0;
+ virtual void openImmersiveSpace() = 0;
+ virtual void dismissImmersiveSpace() = 0;
+};
+#endif
+
} // QNativeInterface
QT_END_NAMESPACE
diff --git a/src/gui/kernel/qguivariant.cpp b/src/gui/kernel/qguivariant.cpp
index 4fdc744029..78a1660355 100644
--- a/src/gui/kernel/qguivariant.cpp
+++ b/src/gui/kernel/qguivariant.cpp
@@ -56,7 +56,8 @@ QT_BEGIN_NAMESPACE
namespace {
-static const struct : QMetaTypeModuleHelper
+// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor): this is not a base class
+static constexpr struct : QMetaTypeModuleHelper
{
#define QT_IMPL_METATYPEINTERFACE_GUI_TYPES(MetaTypeName, MetaTypeId, RealName) \
QT_METATYPE_INTERFACE_INIT(RealName),
@@ -77,7 +78,9 @@ static const struct : QMetaTypeModuleHelper
// either two nullptrs from canConvert, or two valid pointers
Q_ASSERT(onlyCheck || (bool(from) && bool(to)));
+#if QT_CONFIG(shortcut)
using Int = int;
+#endif
switch (makePair(toTypeId, fromTypeId)) {
QMETATYPE_CONVERTER(QByteArray, QColor,
result = source.name(source.alpha() != 255 ?
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
index 189f31fd0a..d6deb8a72a 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -172,7 +172,7 @@ inline QMargins scale(const QMargins &margins, qreal scaleFactor, QPoint origin
template<typename T>
QList<T> scale(const QList<T> &list, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- if (!QHighDpiScaling::isActive())
+ if (qFuzzyCompare(scaleFactor, qreal(1)))
return list;
QList<T> scaled;
@@ -184,7 +184,7 @@ QList<T> scale(const QList<T> &list, qreal scaleFactor, QPoint origin = QPoint(0
inline QRegion scale(const QRegion &region, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- if (!QHighDpiScaling::isActive())
+ if (qFuzzyCompare(scaleFactor, qreal(1)))
return region;
QRegion scaled = region.translated(-origin);
diff --git a/src/gui/kernel/qinputdevice.cpp b/src/gui/kernel/qinputdevice.cpp
index f20351a562..f7b216dcf0 100644
--- a/src/gui/kernel/qinputdevice.cpp
+++ b/src/gui/kernel/qinputdevice.cpp
@@ -384,7 +384,7 @@ QDebug operator<<(QDebug debug, const QInputDevice *device)
debug << "QInputDevice(";
debug << '"' << device->name() << "\", type=" << device->type()
- << Qt::hex << ", ID=" << device->systemId() << ", seat='" << device->seatName() << "'";
+ << ", ID=" << device->systemId() << ", seat='" << device->seatName() << "'";
debug << ')';
return debug;
}
diff --git a/src/gui/kernel/qkeymapper.cpp b/src/gui/kernel/qkeymapper.cpp
index aa8eca5214..4ceb508465 100644
--- a/src/gui/kernel/qkeymapper.cpp
+++ b/src/gui/kernel/qkeymapper.cpp
@@ -9,6 +9,7 @@
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
+#include <qpa/qplatformkeymapper.h>
QT_BEGIN_NAMESPACE
@@ -23,8 +24,7 @@ QT_BEGIN_NAMESPACE
/*!
Constructs a new key mapper.
*/
-QKeyMapper::QKeyMapper()
- : QObject(*new QKeyMapperPrivate, nullptr)
+QKeyMapper::QKeyMapper() : QObject()
{
}
@@ -35,34 +35,34 @@ QKeyMapper::~QKeyMapper()
{
}
-static QList<int> extractKeyFromEvent(QKeyEvent *e)
+QList<QKeyCombination> QKeyMapper::possibleKeys(const QKeyEvent *e)
{
- QList<int> result;
- if (e->key() && (e->key() != Qt::Key_unknown))
- result << e->keyCombination().toCombined();
- else if (!e->text().isEmpty())
- result << int(e->text().at(0).unicode() + (int)e->modifiers());
- return result;
-}
+ qCDebug(lcQpaKeyMapper).verbosity(3) << "Computing possible key combinations for" << e;
-QList<int> QKeyMapper::possibleKeys(QKeyEvent *e)
-{
- return instance()->d_func()->possibleKeys(e);
-}
+ const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ const auto *platformKeyMapper = platformIntegration->keyMapper();
+ QList<QKeyCombination> result = platformKeyMapper->possibleKeyCombinations(e);
-extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); // in qapplication_*.cpp
-void QKeyMapper::changeKeyboard()
-{
- // ## TODO: Support KeyboardLayoutChange on QPA
-#if 0
- // inform all toplevel widgets of the change
- QEvent e(QEvent::KeyboardLayoutChange);
- QWidgetList list = QApplication::topLevelWidgets();
- for (int i = 0; i < list.size(); ++i) {
- QWidget *w = list.at(i);
- qt_sendSpontaneousEvent(w, &e);
+ if (result.isEmpty()) {
+ if (e->key() && (e->key() != Qt::Key_unknown))
+ result << e->keyCombination();
+ else if (!e->text().isEmpty())
+ result << (Qt::Key(e->text().at(0).unicode()) | e->modifiers());
+ }
+
+#if QT_CONFIG(shortcut)
+ if (lcQpaKeyMapper().isDebugEnabled()) {
+ qCDebug(lcQpaKeyMapper) << "Resulting possible key combinations:";
+ for (auto keyCombination : result) {
+ auto keySequence = QKeySequence(keyCombination);
+ qCDebug(lcQpaKeyMapper).verbosity(0) << "\t-"
+ << keyCombination << "/" << keySequence << "/"
+ << qUtf8Printable(keySequence.toString(QKeySequence::NativeText));
+ }
}
#endif
+
+ return result;
}
Q_GLOBAL_STATIC(QKeyMapper, keymapper)
@@ -75,30 +75,6 @@ QKeyMapper *QKeyMapper::instance()
return keymapper();
}
-QKeyMapperPrivate *qt_keymapper_private()
-{
- return QKeyMapper::instance()->d_func();
-}
-
-QKeyMapperPrivate::QKeyMapperPrivate()
-{
- keyboardInputLocale = QLocale::system();
- keyboardInputDirection = keyboardInputLocale.textDirection();
-}
-
-QKeyMapperPrivate::~QKeyMapperPrivate()
-{
-}
-
-QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent *e)
-{
- QList<int> result = QGuiApplicationPrivate::platformIntegration()->possibleKeys(e);
- if (!result.isEmpty())
- return result;
-
- return extractKeyFromEvent(e);
-}
-
void *QKeyMapper::resolveInterface(const char *name, int revision) const
{
Q_UNUSED(name); Q_UNUSED(revision);
diff --git a/src/gui/kernel/qkeymapper_p.h b/src/gui/kernel/qkeymapper_p.h
index 96c5620665..1a6a9a608f 100644
--- a/src/gui/kernel/qkeymapper_p.h
+++ b/src/gui/kernel/qkeymapper_p.h
@@ -25,7 +25,6 @@
QT_BEGIN_NAMESPACE
-class QKeyMapperPrivate;
class Q_GUI_EXPORT QKeyMapper : public QObject
{
Q_OBJECT
@@ -34,35 +33,14 @@ public:
~QKeyMapper();
static QKeyMapper *instance();
- static void changeKeyboard();
- static QList<int> possibleKeys(QKeyEvent *e);
+ static QList<QKeyCombination> possibleKeys(const QKeyEvent *e);
QT_DECLARE_NATIVE_INTERFACE_ACCESSOR(QKeyMapper)
private:
- friend QKeyMapperPrivate *qt_keymapper_private();
- Q_DECLARE_PRIVATE(QKeyMapper)
Q_DISABLE_COPY_MOVE(QKeyMapper)
};
-struct KeyboardLayoutItem;
-class QKeyEvent;
-
-class QKeyMapperPrivate : public QObjectPrivate
-{
- Q_DECLARE_PUBLIC(QKeyMapper)
-public:
- QKeyMapperPrivate();
- ~QKeyMapperPrivate();
-
- QList<int> possibleKeys(QKeyEvent *e);
-
- QLocale keyboardInputLocale;
- Qt::LayoutDirection keyboardInputDirection;
-};
-
-QKeyMapperPrivate *qt_keymapper_private(); // from qkeymapper.cpp
-
// ----------------- QNativeInterface -----------------
namespace QNativeInterface::Private {
diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp
index ffd19f8b2f..0529d940d2 100644
--- a/src/gui/kernel/qkeysequence.cpp
+++ b/src/gui/kernel/qkeysequence.cpp
@@ -13,7 +13,7 @@
#endif
#include "qvariant.h"
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
#include <QtCore/private/qcore_mac_p.h>
#endif
@@ -24,11 +24,11 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-#if defined(Q_OS_MACOS) || defined(Q_QDOC)
+#if defined(Q_OS_APPLE) || defined(Q_QDOC)
Q_CONSTINIT static bool qt_sequence_no_mnemonics = true;
-struct MacSpecialKey {
+struct AppleSpecialKey {
int key;
- ushort macSymbol;
+ ushort appleSymbol;
};
// Unicode code points for the glyphs associated with these keys
@@ -38,7 +38,7 @@ static constexpr int kControlUnicode = 0x2303;
static constexpr int kOptionUnicode = 0x2325;
static constexpr int kCommandUnicode = 0x2318;
-static constexpr MacSpecialKey entries[] = {
+static constexpr AppleSpecialKey entries[] = {
{ Qt::Key_Escape, 0x238B },
{ Qt::Key_Tab, 0x21E5 },
{ Qt::Key_Backtab, 0x21E4 },
@@ -63,45 +63,45 @@ static constexpr MacSpecialKey entries[] = {
{ Qt::Key_Eject, 0x23CF },
};
-static constexpr bool operator<(const MacSpecialKey &lhs, const MacSpecialKey &rhs)
+static constexpr bool operator<(const AppleSpecialKey &lhs, const AppleSpecialKey &rhs)
{
return lhs.key < rhs.key;
}
-static constexpr bool operator<(const MacSpecialKey &lhs, int rhs)
+static constexpr bool operator<(const AppleSpecialKey &lhs, int rhs)
{
return lhs.key < rhs;
}
-static constexpr bool operator<(int lhs, const MacSpecialKey &rhs)
+static constexpr bool operator<(int lhs, const AppleSpecialKey &rhs)
{
return lhs < rhs.key;
}
static_assert(q20::is_sorted(std::begin(entries), std::end(entries)));
-QChar qt_macSymbolForQtKey(int key)
+static QChar appleSymbolForQtKey(int key)
{
const auto i = std::lower_bound(std::begin(entries), std::end(entries), key);
if (i == std::end(entries) || key < *i)
return QChar();
- ushort macSymbol = i->macSymbol;
+ ushort appleSymbol = i->appleSymbol;
if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)
- && (macSymbol == kControlUnicode || macSymbol == kCommandUnicode)) {
- if (macSymbol == kControlUnicode)
- macSymbol = kCommandUnicode;
+ && (appleSymbol == kControlUnicode || appleSymbol == kCommandUnicode)) {
+ if (appleSymbol == kControlUnicode)
+ appleSymbol = kCommandUnicode;
else
- macSymbol = kControlUnicode;
+ appleSymbol = kControlUnicode;
}
- return QChar(macSymbol);
+ return QChar(appleSymbol);
}
-static int qtkeyForMacSymbol(const QChar ch)
+static int qtkeyForAppleSymbol(const QChar ch)
{
const ushort unicode = ch.unicode();
- for (const MacSpecialKey &entry : entries) {
- if (entry.macSymbol == unicode) {
+ for (const AppleSpecialKey &entry : entries) {
+ if (entry.appleSymbol == unicode) {
int key = entry.key;
if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)
&& (unicode == kControlUnicode || unicode == kCommandUnicode)) {
@@ -191,7 +191,7 @@ void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemoni
QKeySequence objects can be cast to a QString to obtain a human-readable
translated version of the sequence. Similarly, the toString() function
- produces human-readable strings for use in menus. On \macos, the
+ produces human-readable strings for use in menus. On Apple platforms, the
appropriate symbols are used to describe keyboard shortcuts using special
keys on the Macintosh keyboard.
@@ -199,12 +199,12 @@ void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemoni
code point of the character; for example, 'A' gives the same key sequence
as Qt::Key_A.
- \note On \macos, references to "Ctrl", Qt::CTRL, Qt::Key_Control
+ \note On Apple platforms, references to "Ctrl", Qt::CTRL, Qt::Key_Control
and Qt::ControlModifier correspond to the \uicontrol Command keys on the
Macintosh keyboard, and references to "Meta", Qt::META, Qt::Key_Meta and
- Qt::MetaModifier correspond to the \uicontrol Control keys. Developers on
- \macos can use the same shortcut descriptions across all platforms,
- and their applications will automatically work as expected on \macos.
+ Qt::MetaModifier correspond to the \uicontrol Control keys. In effect,
+ developers can use the same shortcut descriptions across all platforms,
+ and their applications will automatically work as expected on Apple platforms.
\section1 Standard Shortcuts
@@ -213,21 +213,21 @@ void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemoni
setting up actions in a typical application. The table below shows
some common key sequences that are often used for these standard
shortcuts by applications on four widely-used platforms. Note
- that on \macos, the \uicontrol Ctrl value corresponds to the \uicontrol
+ that on Apple platforms, the \uicontrol Ctrl value corresponds to the \uicontrol
Command keys on the Macintosh keyboard, and the \uicontrol Meta value
corresponds to the \uicontrol Control keys.
\table
- \header \li StandardKey \li Windows \li \macos \li KDE Plasma \li GNOME
+ \header \li StandardKey \li Windows \li Apple platforms \li KDE Plasma \li GNOME
\row \li HelpContents \li F1 \li Ctrl+? \li F1 \li F1
\row \li WhatsThis \li Shift+F1 \li Shift+F1 \li Shift+F1 \li Shift+F1
\row \li Open \li Ctrl+O \li Ctrl+O \li Ctrl+O \li Ctrl+O
\row \li Close \li Ctrl+F4, Ctrl+W \li Ctrl+W, Ctrl+F4 \li Ctrl+W \li Ctrl+W
\row \li Save \li Ctrl+S \li Ctrl+S \li Ctrl+S \li Ctrl+S
\row \li Quit \li \li Ctrl+Q \li Ctrl+Q \li Ctrl+Q
- \row \li SaveAs \li \li Ctrl+Shift+S \li \li Ctrl+Shift+S
+ \row \li SaveAs \li Ctrl+Shift+S \li Ctrl+Shift+S \li Ctrl+Shift+S \li Ctrl+Shift+S
\row \li New \li Ctrl+N \li Ctrl+N \li Ctrl+N \li Ctrl+N
- \row \li Delete \li Del \li Del, Meta+D \li Del, Ctrl+D \li Del, Ctrl+D
+ \row \li Delete \li Del \li Forward Delete, Meta+D \li Del, Ctrl+D \li Del, Ctrl+D
\row \li Cut \li Ctrl+X, Shift+Del \li Ctrl+X, Meta+K \li Ctrl+X, F20, Shift+Del \li Ctrl+X, F20, Shift+Del
\row \li Copy \li Ctrl+C, Ctrl+Ins \li Ctrl+C \li Ctrl+C, F16, Ctrl+Ins \li Ctrl+C, F16, Ctrl+Ins
\row \li Paste \li Ctrl+V, Shift+Ins \li Ctrl+V, Meta+Y \li Ctrl+V, F18, Shift+Ins \li Ctrl+V, F18, Shift+Ins
@@ -287,7 +287,7 @@ void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemoni
\row \li DeleteCompleteLine \li (none) \li (none) \li Ctrl+U \li Ctrl+U
\row \li InsertParagraphSeparator \li Enter \li Enter \li Enter \li Enter
\row \li InsertLineSeparator \li Shift+Enter \li Meta+Enter, Meta+O \li Shift+Enter \li Shift+Enter
- \row \li Backspace \li (none) \li Meta+H \li (none) \li (none)
+ \row \li Backspace \li (none) \li Delete, Meta+H \li (none) \li (none)
\row \li Cancel \li Escape \li Escape, Ctrl+. \li Escape \li Escape
\endtable
@@ -374,7 +374,7 @@ void Q_GUI_EXPORT qt_set_sequence_auto_mnemonic(bool b) { qt_sequence_no_mnemoni
\enum QKeySequence::SequenceFormat
\value NativeText The key sequence as a platform specific string.
- This means that it will be shown translated and on the Mac it will
+ This means that it will be shown translated and on Apple platforms it will
resemble a key sequence from the menu bar. This enum is best used when you
want to display the string to the user.
@@ -710,7 +710,7 @@ static constexpr int numKeyNames = sizeof keyname / sizeof *keyname;
\value InsertLineSeparator Insert a new line.
\value InsertParagraphSeparator Insert a new paragraph.
\value Italic Italic text.
- \value MoveToEndOfBlock Move cursor to end of block. This shortcut is only used on the \macos.
+ \value MoveToEndOfBlock Move cursor to end of block. This shortcut is only used on Apple platforms.
\value MoveToEndOfDocument Move cursor to end of document.
\value MoveToEndOfLine Move cursor to end of line.
\value MoveToNextChar Move cursor to next character.
@@ -721,7 +721,7 @@ static constexpr int numKeyNames = sizeof keyname / sizeof *keyname;
\value MoveToPreviousLine Move cursor to previous line.
\value MoveToPreviousPage Move cursor to previous page.
\value MoveToPreviousWord Move cursor to previous word.
- \value MoveToStartOfBlock Move cursor to start of a block. This shortcut is only used on \macos.
+ \value MoveToStartOfBlock Move cursor to start of a block. This shortcut is only used on Apple platforms.
\value MoveToStartOfDocument Move cursor to start of document.
\value MoveToStartOfLine Move cursor to start of line.
\value New Create new document.
@@ -739,7 +739,7 @@ static constexpr int numKeyNames = sizeof keyname / sizeof *keyname;
\value Save Save document.
\value SelectAll Select all text.
\value Deselect Deselect text. Since 5.1
- \value SelectEndOfBlock Extend selection to the end of a text block. This shortcut is only used on \macos.
+ \value SelectEndOfBlock Extend selection to the end of a text block. This shortcut is only used on Apple platforms.
\value SelectEndOfDocument Extend selection to end of document.
\value SelectEndOfLine Extend selection to end of line.
\value SelectNextChar Extend selection to next character.
@@ -750,7 +750,7 @@ static constexpr int numKeyNames = sizeof keyname / sizeof *keyname;
\value SelectPreviousLine Extend selection to previous line.
\value SelectPreviousPage Extend selection to previous page.
\value SelectPreviousWord Extend selection to previous word.
- \value SelectStartOfBlock Extend selection to the start of a text block. This shortcut is only used on \macos.
+ \value SelectStartOfBlock Extend selection to the start of a text block. This shortcut is only used on Apple platforms.
\value SelectStartOfDocument Extend selection to start of document.
\value SelectStartOfLine Extend selection to start of line.
\value Underline Underline text.
@@ -784,8 +784,8 @@ QKeySequence::QKeySequence(StandardKey key)
{
const QList <QKeySequence> bindings = keyBindings(key);
//pick only the first/primary shortcut from current bindings
- if (bindings.size() > 0) {
- d = bindings.first().d;
+ if (!bindings.isEmpty()) {
+ d = bindings.constFirst().d;
d->ref.ref();
}
else
@@ -1018,7 +1018,7 @@ int QKeySequence::assign(const QString &ks, QKeySequence::SequenceFormat format)
}
QString part = keyseq.left(-1 == p ? keyseq.size() : p - diff);
keyseq = keyseq.right(-1 == p ? 0 : keyseq.size() - (p + 1));
- d->key[n] = QKeySequencePrivate::decodeString(std::move(part), format);
+ d->key[n] = QKeySequencePrivate::decodeString(std::move(part), format).toCombined();
++n;
}
return n;
@@ -1036,15 +1036,7 @@ Q_DECLARE_TYPEINFO(QModifKeyName, Q_RELOCATABLE_TYPE);
Q_GLOBAL_STATIC(QList<QModifKeyName>, globalModifs)
Q_GLOBAL_STATIC(QList<QModifKeyName>, globalPortableModifs)
-/*!
- Constructs a single key from the string \a str.
-*/
-int QKeySequence::decodeString(const QString &str)
-{
- return QKeySequencePrivate::decodeString(str, NativeText);
-}
-
-int QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceFormat format)
+QKeyCombination QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceFormat format)
{
Q_ASSERT(!accel.isEmpty());
@@ -1056,7 +1048,7 @@ int QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceForma
if (nativeText) {
gmodifs = globalModifs();
if (gmodifs->isEmpty()) {
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
if (dontSwap)
*gmodifs << QModifKeyName(Qt::META, QChar(kCommandUnicode));
@@ -1098,7 +1090,7 @@ int QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceForma
modifs += *gmodifs; // Test non-translated ones last
QString sl = accel;
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
for (int i = 0; i < modifs.size(); ++i) {
const QModifKeyName &mkf = modifs.at(i);
if (sl.contains(mkf.name)) {
@@ -1153,8 +1145,8 @@ int QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceForma
int fnum = 0;
if (accelRef.size() == 1) {
-#if defined(Q_OS_MACOS)
- int qtKey = qtkeyForMacSymbol(accelRef.at(0));
+#if defined(Q_OS_APPLE)
+ int qtKey = qtkeyForAppleSymbol(accelRef.at(0));
if (qtKey != -1) {
ret |= qtKey;
} else
@@ -1189,17 +1181,7 @@ int QKeySequencePrivate::decodeString(QString accel, QKeySequence::SequenceForma
if (!found)
return Qt::Key_unknown;
}
- return ret;
-}
-
-/*!
- Creates a shortcut string for \a key. For example,
- Qt::CTRL+Qt::Key_O gives "Ctrl+O". The strings, "Ctrl", "Shift", etc. are
- translated (using QObject::tr()) in the "QShortcut" context.
- */
-QString QKeySequence::encodeString(int key)
-{
- return QKeySequencePrivate::encodeString(key, NativeText);
+ return QKeyCombination::fromCombined(ret);
}
static inline void addKey(QString &str, const QString &theKey, QKeySequence::SequenceFormat format)
@@ -1216,20 +1198,24 @@ static inline void addKey(QString &str, const QString &theKey, QKeySequence::Seq
str += theKey;
}
-QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat format)
+QString QKeySequencePrivate::encodeString(QKeyCombination keyCombination, QKeySequence::SequenceFormat format)
{
bool nativeText = (format == QKeySequence::NativeText);
QString s;
+ const auto key = keyCombination.key();
+
// Handle -1 (Invalid Key) and Qt::Key_unknown gracefully
- if (key == -1 || key == Qt::Key_unknown)
+ if (keyCombination.toCombined() == -1 || key == Qt::Key_unknown)
return s;
-#if defined(Q_OS_MACOS)
+ const auto modifiers = keyCombination.keyboardModifiers();
+
+#if defined(Q_OS_APPLE)
if (nativeText) {
- // On OS X the order (by default) is Meta, Alt, Shift, Control.
+ // On Apple platforms the order (by default) is Meta, Alt, Shift, Control.
// If the AA_MacDontSwapCtrlAndMeta is enabled, then the order
- // is Ctrl, Alt, Shift, Meta. The macSymbolForQtKey does this swap
+ // is Ctrl, Alt, Shift, Meta. The appleSymbolForQtKey helper does this swap
// for us, which means that we have to adjust our order here.
// The upshot is a lot more infrastructure to keep the number of
// if tests down and the code relatively clean.
@@ -1248,33 +1234,33 @@ QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat
}
for (int i = 0; modifierOrder[i] != 0; ++i) {
- if (key & modifierOrder[i])
- s += qt_macSymbolForQtKey(qtkeyOrder[i]);
+ if (modifiers & modifierOrder[i])
+ s += appleSymbolForQtKey(qtkeyOrder[i]);
}
} else
#endif
{
// On other systems the order is Meta, Control, Alt, Shift
- if ((key & Qt::META) == Qt::META)
+ if (modifiers & Qt::MetaModifier)
s = nativeText ? QCoreApplication::translate("QShortcut", "Meta") : QString::fromLatin1("Meta");
- if ((key & Qt::CTRL) == Qt::CTRL)
+ if (modifiers & Qt::ControlModifier)
addKey(s, nativeText ? QCoreApplication::translate("QShortcut", "Ctrl") : QString::fromLatin1("Ctrl"), format);
- if ((key & Qt::ALT) == Qt::ALT)
+ if (modifiers & Qt::AltModifier)
addKey(s, nativeText ? QCoreApplication::translate("QShortcut", "Alt") : QString::fromLatin1("Alt"), format);
- if ((key & Qt::SHIFT) == Qt::SHIFT)
+ if (modifiers & Qt::ShiftModifier)
addKey(s, nativeText ? QCoreApplication::translate("QShortcut", "Shift") : QString::fromLatin1("Shift"), format);
}
- if ((key & Qt::KeypadModifier) == Qt::KeypadModifier)
+ if (modifiers & Qt::KeypadModifier)
addKey(s, nativeText ? QCoreApplication::translate("QShortcut", "Num") : QString::fromLatin1("Num"), format);
- QString p = keyName(key, format);
+ QString keyName = QKeySequencePrivate::keyName(key, format);
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
if (nativeText)
- s += p;
+ s += keyName;
else
#endif
- addKey(s, p, format);
+ addKey(s, keyName, format);
return s;
}
@@ -1286,10 +1272,9 @@ QString QKeySequencePrivate::encodeString(int key, QKeySequence::SequenceFormat
This static method is used by encodeString() and by the D-Bus menu exporter.
*/
-QString QKeySequencePrivate::keyName(int key, QKeySequence::SequenceFormat format)
+QString QKeySequencePrivate::keyName(Qt::Key key, QKeySequence::SequenceFormat format)
{
bool nativeText = (format == QKeySequence::NativeText);
- key &= ~(Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier);
QString p;
if (key && key < Qt::Key_Escape && key != Qt::Key_Space) {
@@ -1304,9 +1289,9 @@ QString QKeySequencePrivate::keyName(int key, QKeySequence::SequenceFormat forma
: QString::fromLatin1("F%1").arg(key - Qt::Key_F1 + 1);
} else if (key) {
int i=0;
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
if (nativeText) {
- QChar ch = qt_macSymbolForQtKey(key);
+ QChar ch = appleSymbolForQtKey(key);
if (!ch.isNull())
p = ch;
else
@@ -1314,7 +1299,7 @@ QString QKeySequencePrivate::keyName(int key, QKeySequence::SequenceFormat forma
} else
#endif
{
-#if defined(Q_OS_MACOS)
+#if defined(Q_OS_APPLE)
NonSymbol:
#endif
while (i < numKeyNames) {
@@ -1504,7 +1489,7 @@ bool QKeySequence::isDetached() const
If the key sequence has no keys, an empty string is returned.
- On \macos, the string returned resembles the sequence that is
+ On Apple platforms, the string returned resembles the sequence that is
shown in the menu bar if \a format is
QKeySequence::NativeText; otherwise, the string uses the
"portable" format, suitable for writing to a file.
@@ -1518,7 +1503,7 @@ QString QKeySequence::toString(SequenceFormat format) const
// look like our latin case on Windows and X11
int end = count();
for (int i = 0; i < end; ++i) {
- finalString += d->encodeString(d->key[i], format);
+ finalString += d->encodeString(QKeyCombination::fromCombined(d->key[i]), format);
finalString += ", "_L1;
}
finalString.truncate(finalString.size() - 2);
diff --git a/src/gui/kernel/qkeysequence.h b/src/gui/kernel/qkeysequence.h
index 44fd1f9cca..4dae26a755 100644
--- a/src/gui/kernel/qkeysequence.h
+++ b/src/gui/kernel/qkeysequence.h
@@ -115,6 +115,7 @@ public:
NativeText,
PortableText
};
+ Q_ENUM(SequenceFormat)
QKeySequence();
QKeySequence(const QString &key, SequenceFormat format = NativeText);
@@ -135,6 +136,7 @@ public:
PartialMatch,
ExactMatch
};
+ Q_ENUM(SequenceMatch);
QString toString(SequenceFormat format = PortableText) const;
static QKeySequence fromString(const QString &str, SequenceFormat format = PortableText);
@@ -165,8 +167,6 @@ public:
bool isDetached() const;
private:
- static int decodeString(const QString &ks);
- static QString encodeString(int key);
int assign(const QString &str);
int assign(const QString &str, SequenceFormat format);
void setKey(QKeyCombination key, int index);
diff --git a/src/gui/kernel/qkeysequence_p.h b/src/gui/kernel/qkeysequence_p.h
index 2f9a0b09aa..8b98e3778a 100644
--- a/src/gui/kernel/qkeysequence_p.h
+++ b/src/gui/kernel/qkeysequence_p.h
@@ -35,7 +35,7 @@ struct QKeyBinding
class QKeySequencePrivate
{
public:
- enum { MaxKeyCount = 4 }; // also used in QKeySequenceEdit
+ static constexpr int MaxKeyCount = 4 ; // also used in QKeySequenceEdit
constexpr QKeySequencePrivate() : ref(1), key{} {}
inline QKeySequencePrivate(const QKeySequencePrivate &copy) : ref(1)
{
@@ -44,10 +44,10 @@ public:
}
QAtomicInt ref;
int key[MaxKeyCount];
- static QString encodeString(int key, QKeySequence::SequenceFormat format);
+ static QString encodeString(QKeyCombination keyCombination, QKeySequence::SequenceFormat format);
// used in dbusmenu
- Q_GUI_EXPORT static QString keyName(int key, QKeySequence::SequenceFormat format);
- static int decodeString(QString accel, QKeySequence::SequenceFormat format);
+ Q_GUI_EXPORT static QString keyName(Qt::Key key, QKeySequence::SequenceFormat format);
+ static QKeyCombination decodeString(QString accel, QKeySequence::SequenceFormat format);
};
QT_END_NAMESPACE
diff --git a/src/gui/kernel/qopenglcontext.cpp b/src/gui/kernel/qopenglcontext.cpp
index 1dab68e579..dd41318f72 100644
--- a/src/gui/kernel/qopenglcontext.cpp
+++ b/src/gui/kernel/qopenglcontext.cpp
@@ -138,6 +138,22 @@ QOpenGLContext *qt_gl_global_share_context()
application is portable between different platforms. However, if you use
QOpenGLFunctions::glBindFramebuffer(), this is done automatically for you.
+ \warning WebAssembly
+
+ We recommend that only one QOpenGLContext is made current with a QSurface,
+ for the entire lifetime of the QSurface. Should more than once context be used,
+ it is important to understand that multiple QOpenGLContext instances may be
+ backed by the same native context underneath with the WebAssembly platform.
+ Therefore, calling makeCurrent() with the same QSurface on two QOpenGLContext
+ objects may not switch to a different native context in the second call. As
+ a result, any OpenGL state changes done after the second makeCurrent() may
+ alter the state of the first QOpenGLContext as well, as they are all backed
+ by the same native context.
+
+ \note This means that when targeting WebAssembly with existing OpenGL-based
+ Qt code, some porting may be required to cater to these limitations.
+
+
\sa QOpenGLFunctions, QOpenGLBuffer, QOpenGLShaderProgram, QOpenGLFramebufferObject
*/
@@ -154,6 +170,9 @@ QOpenGLContext *QOpenGLContextPrivate::setCurrentContext(QOpenGLContext *context
qWarning("No QTLS available. currentContext won't work");
return nullptr;
}
+ if (!context)
+ return nullptr;
+
threadContext = new QGuiGLThreadContext;
qwindow_context_storage()->setLocalData(threadContext);
}
@@ -452,6 +471,11 @@ void QOpenGLContext::destroy()
If you wish to make the context current in order to do clean-up, make sure
to only connect to the signal using a direct connection.
+
+ \note In Qt for Python, this signal will not be received when emitted
+ from the destructor of QOpenGLWidget or QOpenGLWindow due to the Python
+ instance already being destroyed. We recommend doing cleanups
+ in QWidget::hideEvent() instead.
*/
/*!
diff --git a/src/gui/kernel/qpaintdevicewindow.cpp b/src/gui/kernel/qpaintdevicewindow.cpp
index bc7ac89b03..9e8c6ae5a8 100644
--- a/src/gui/kernel/qpaintdevicewindow.cpp
+++ b/src/gui/kernel/qpaintdevicewindow.cpp
@@ -171,6 +171,8 @@ bool QPaintDeviceWindow::event(QEvent *event)
auto region = QRect(QPoint(0, 0), size());
d->doFlush(region); // Will end up calling paintEvent
return true;
+ } else if (event->type() == QEvent::Resize) {
+ d->handleResizeEvent();
}
return QWindow::event(event);
diff --git a/src/gui/kernel/qpaintdevicewindow_p.h b/src/gui/kernel/qpaintdevicewindow_p.h
index 32db2cb7a4..85c11cbf91 100644
--- a/src/gui/kernel/qpaintdevicewindow_p.h
+++ b/src/gui/kernel/qpaintdevicewindow_p.h
@@ -31,6 +31,8 @@ public:
QPaintDeviceWindowPrivate();
~QPaintDeviceWindowPrivate() override;
+ virtual void handleResizeEvent() {}
+
virtual void beginPaint(const QRegion &region)
{
Q_UNUSED(region);
@@ -82,7 +84,7 @@ public:
void markWindowAsDirty()
{
Q_Q(QPaintDeviceWindow);
- dirtyRegion += QRect(QPoint(0, 0), q->size());
+ dirtyRegion = QRect(QPoint(0, 0), q->size());
}
private:
diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp
index ac7a1a9fef..e308b796ab 100644
--- a/src/gui/kernel/qpalette.cpp
+++ b/src/gui/kernel/qpalette.cpp
@@ -1,8 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qpalette.h"
-#include "qguiapplication.h"
+#include "qpalette_p.h"
#include "qguiapplication_p.h"
#include "qdatastream.h"
#include "qvariant.h"
@@ -12,65 +11,11 @@
QT_BEGIN_NAMESPACE
-static int qt_palette_count = 1;
-
-static constexpr QPalette::ResolveMask colorRoleOffset(QPalette::ColorGroup colorGroup)
-{
- // Exclude NoRole; that bit is used for AccentColor
- return (qToUnderlying(QPalette::NColorRoles) - 1) * qToUnderlying(colorGroup);
-}
-
-static constexpr QPalette::ResolveMask bitPosition(QPalette::ColorGroup colorGroup,
- QPalette::ColorRole colorRole)
-{
- // Map AccentColor into NoRole for resolving purposes
- if (colorRole == QPalette::AccentColor)
- colorRole = QPalette::NoRole;
-
- return colorRole + colorRoleOffset(colorGroup);
-}
-
-static_assert(bitPosition(QPalette::ColorGroup(QPalette::NColorGroups - 1),
+static_assert(QPalettePrivate::bitPosition(QPalette::ColorGroup(QPalette::NColorGroups - 1),
QPalette::ColorRole(QPalette::NColorRoles - 1))
< sizeof(QPalette::ResolveMask) * CHAR_BIT,
"The resolve mask type is not wide enough to fit the entire bit mask.");
-class QPalettePrivate
-{
-public:
- class Data : public QSharedData {
- public:
- // Every instance of Data has to have a unique serial number, even
- // if it gets created by copying another - we wouldn't create a copy
- // in the first place if the serial number should be the same!
- Data(const Data &other)
- : QSharedData(other)
- {
- for (int grp = 0; grp < int(QPalette::NColorGroups); grp++) {
- for (int role = 0; role < int(QPalette::NColorRoles); role++)
- br[grp][role] = other.br[grp][role];
- }
- }
- Data() = default;
-
- QBrush br[QPalette::NColorGroups][QPalette::NColorRoles];
- const int ser_no = qt_palette_count++;
- };
-
- QPalettePrivate(const QExplicitlySharedDataPointer<Data> &data)
- : ref(1), data(data)
- { }
- QPalettePrivate()
- : QPalettePrivate(QExplicitlySharedDataPointer<Data>(new Data))
- { }
-
- QAtomicInt ref;
- QPalette::ResolveMask resolveMask = {0};
- static inline int qt_palette_private_count = 0;
- int detach_no = ++qt_palette_private_count;
- QExplicitlySharedDataPointer<Data> data;
-};
-
static QColor qt_mix_colors(QColor a, QColor b)
{
return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2,
@@ -113,12 +58,12 @@ static void qt_ensure_default_accent_color(QPalette &pal)
// Act only for color groups where no accent color is set
for (int i = 0; i < QPalette::NColorGroups; ++i) {
const QPalette::ColorGroup group = static_cast<QPalette::ColorGroup>(i);
- if (!pal.isBrushSet(group, QPalette::AccentColor)) {
+ if (!pal.isBrushSet(group, QPalette::Accent)) {
// Default to highlight if available, otherwise use a shade of base
const QBrush accentBrush = pal.isBrushSet(group, QPalette::Highlight)
? pal.brush(group, QPalette::Highlight)
: pal.brush(group, QPalette::Base).color().lighter(lighter);
- pal.setBrush(group, QPalette::AccentColor, accentBrush);
+ pal.setBrush(group, QPalette::Accent, accentBrush);
}
}
}
@@ -349,6 +294,15 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button)
*/
/*!
+ \fn const QBrush & QPalette::accent() const
+ \since 6.6
+
+ Returns the accent brush of the current color group.
+
+ \sa ColorRole, brush()
+*/
+
+/*!
\fn const QBrush & QPalette::link() const
Returns the unvisited link text brush of the current color group.
@@ -548,8 +502,8 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button)
item. By default, the highlight color is
Qt::darkBlue.
- \value [since 6.6] AccentColor
- A color that typically contrasts or compliments
+ \value [since 6.6] Accent
+ A color that typically contrasts or complements
Base, Window and Button colors. It usually represents
the users' choice of desktop personalisation.
Styling of interactive components is a typical use case.
@@ -840,7 +794,7 @@ void QPalette::setBrush(ColorGroup cg, ColorRole cr, const QBrush &b)
cg = Active;
}
- const auto newResolveMask = d->resolveMask | ResolveMask(1) << bitPosition(cg, cr);
+ const auto newResolveMask = d->resolveMask | ResolveMask(1) << QPalettePrivate::bitPosition(cg, cr);
const auto valueChanged = d->data->br[cg][cr] != b;
if (valueChanged) {
@@ -887,7 +841,7 @@ bool QPalette::isBrushSet(ColorGroup cg, ColorRole cr) const
return false;
}
- return d->resolveMask & (ResolveMask(1) << bitPosition(cg, cr));
+ return d->resolveMask & (ResolveMask(1) << QPalettePrivate::bitPosition(cg, cr));
}
/*!
@@ -996,7 +950,7 @@ static constexpr QPalette::ResolveMask allResolveMask()
QPalette::ResolveMask mask = {0};
for (int role = 0; role < int(QPalette::NColorRoles); ++role) {
for (int grp = 0; grp < int(QPalette::NColorGroups); ++grp) {
- mask |= (QPalette::ResolveMask(1) << bitPosition(QPalette::ColorGroup(grp), QPalette::ColorRole(role)));
+ mask |= (QPalette::ResolveMask(1) << QPalettePrivate::bitPosition(QPalette::ColorGroup(grp), QPalette::ColorRole(role)));
}
}
return mask;
@@ -1004,7 +958,10 @@ static constexpr QPalette::ResolveMask allResolveMask()
/*!
Returns a new QPalette that is a union of this instance and \a other.
- Color roles set in this instance take precedence.
+ Color roles set in this instance take precedence. Roles that are not
+ set in this instance will be taken from \a other.
+
+ \sa isBrushSet
*/
QPalette QPalette::resolve(const QPalette &other) const
{
@@ -1022,12 +979,12 @@ QPalette QPalette::resolve(const QPalette &other) const
palette.detach();
for (int role = 0; role < int(NColorRoles); ++role) {
- // Don't resolve NoRole, its bits are needed for AccentColor (see bitPosition)
+ // Don't resolve NoRole, its bits are needed for Accent (see bitPosition)
if (role == NoRole)
continue;
for (int grp = 0; grp < int(NColorGroups); ++grp) {
- if (!(d->resolveMask & (ResolveMask(1) << bitPosition(ColorGroup(grp), ColorRole(role))))) {
+ if (!(d->resolveMask & (ResolveMask(1) << QPalettePrivate::bitPosition(ColorGroup(grp), ColorRole(role))))) {
palette.d->data.detach();
palette.d->data->br[grp][role] = other.d->data->br[grp][role];
}
@@ -1161,9 +1118,9 @@ QDataStream &operator>>(QDataStream &s, QPalette &p)
p.setBrush(group, (QPalette::ColorRole)role, tmp);
}
- // AccentColor defaults to Highlight for stream versions that don't have it.
+ // Accent defaults to Highlight for stream versions that don't have it.
if (s.version() < QDataStream::Qt_6_6)
- p.setBrush(group, QPalette::AccentColor, p.brush(group, QPalette::Highlight));
+ p.setBrush(group, QPalette::Accent, p.brush(group, QPalette::Highlight));
}
}
@@ -1212,10 +1169,10 @@ void QPalette::setColorGroup(ColorGroup cg, const QBrush &windowText, const QBru
for (int cr = Highlight; cr <= LinkVisited; ++cr) {
if (cg == All) {
for (int group = Active; group < NColorGroups; ++group) {
- d->resolveMask &= ~(ResolveMask(1) << bitPosition(ColorGroup(group), ColorRole(cr)));
+ d->resolveMask &= ~(ResolveMask(1) << QPalettePrivate::bitPosition(ColorGroup(group), ColorRole(cr)));
}
} else {
- d->resolveMask &= ~(ResolveMask(1) << bitPosition(ColorGroup(cg), ColorRole(cr)));
+ d->resolveMask &= ~(ResolveMask(1) << QPalettePrivate::bitPosition(ColorGroup(cg), ColorRole(cr)));
}
}
}
diff --git a/src/gui/kernel/qpalette.h b/src/gui/kernel/qpalette.h
index 92d2502afe..a6162e1090 100644
--- a/src/gui/kernel/qpalette.h
+++ b/src/gui/kernel/qpalette.h
@@ -45,6 +45,7 @@ public:
operator QVariant() const;
// Do not change the order, the serialization format depends on it
+ // Ensure these values are kept in sync with QQuickColorGroup's properties.
enum ColorGroup { Active, Disabled, Inactive, NColorGroups, Current, All, Normal = Active };
Q_ENUM(ColorGroup)
enum ColorRole { WindowText, Button, Light, Midlight, Dark, Mid,
@@ -55,8 +56,8 @@ public:
NoRole,
ToolTipBase, ToolTipText,
PlaceholderText,
- AccentColor,
- NColorRoles = AccentColor + 1,
+ Accent,
+ NColorRoles = Accent + 1,
};
Q_ENUM(ColorRole)
@@ -99,7 +100,7 @@ public:
inline const QBrush &link() const { return brush(Link); }
inline const QBrush &linkVisited() const { return brush(LinkVisited); }
inline const QBrush &placeholderText() const { return brush(PlaceholderText); }
- inline const QBrush &accentColor() const { return brush(AccentColor); }
+ inline const QBrush &accent() const { return brush(Accent); }
bool operator==(const QPalette &p) const;
inline bool operator!=(const QPalette &p) const { return !(operator==(p)); }
diff --git a/src/gui/kernel/qpalette_p.h b/src/gui/kernel/qpalette_p.h
new file mode 100644
index 0000000000..ce7c30d66d
--- /dev/null
+++ b/src/gui/kernel/qpalette_p.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QPALETTE_P_H
+#define QPALETTE_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 "qpalette.h"
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QPalettePrivate
+{
+public:
+ class Data : public QSharedData {
+ public:
+ // Every instance of Data has to have a unique serial number, even
+ // if it gets created by copying another - we wouldn't create a copy
+ // in the first place if the serial number should be the same!
+ Data(const Data &other)
+ : QSharedData(other)
+ {
+ for (int grp = 0; grp < int(QPalette::NColorGroups); grp++) {
+ for (int role = 0; role < int(QPalette::NColorRoles); role++)
+ br[grp][role] = other.br[grp][role];
+ }
+ }
+ Data() = default;
+
+ QBrush br[QPalette::NColorGroups][QPalette::NColorRoles];
+ const int ser_no = qt_palette_count++;
+ };
+
+ QPalettePrivate(const QExplicitlySharedDataPointer<Data> &data)
+ : ref(1), data(data)
+ { }
+ QPalettePrivate()
+ : QPalettePrivate(QExplicitlySharedDataPointer<Data>(new Data))
+ { }
+
+ static constexpr QPalette::ResolveMask colorRoleOffset(QPalette::ColorGroup colorGroup)
+ {
+ // Exclude NoRole; that bit is used for Accent
+ return (qToUnderlying(QPalette::NColorRoles) - 1) * qToUnderlying(colorGroup);
+ }
+
+ static constexpr QPalette::ResolveMask bitPosition(QPalette::ColorGroup colorGroup,
+ QPalette::ColorRole colorRole)
+ {
+ // Map Accent into NoRole for resolving purposes
+ if (colorRole == QPalette::Accent)
+ colorRole = QPalette::NoRole;
+
+ return colorRole + colorRoleOffset(colorGroup);
+ }
+
+ QAtomicInt ref;
+ QPalette::ResolveMask resolveMask = {0};
+ static inline int qt_palette_count = 0;
+ static inline int qt_palette_private_count = 0;
+ int detach_no = ++qt_palette_private_count;
+ QExplicitlySharedDataPointer<Data> data;
+};
+
+QT_END_NAMESPACE
+
+#endif // QPALETTE_P_H
diff --git a/src/gui/kernel/qplatformdialoghelper.cpp b/src/gui/kernel/qplatformdialoghelper.cpp
index 7153c90f50..93de7933d4 100644
--- a/src/gui/kernel/qplatformdialoghelper.cpp
+++ b/src/gui/kernel/qplatformdialoghelper.cpp
@@ -778,6 +778,8 @@ public:
QPixmap iconPixmap;
QString checkBoxLabel;
Qt::CheckState checkBoxState = Qt::Unchecked;
+ int defaultButtonId = 0;
+ int escapeButtonId = 0;
QMessageDialogOptions::Options options;
};
@@ -881,9 +883,9 @@ QPlatformDialogHelper::StandardButtons QMessageDialogOptions::standardButtons()
}
int QMessageDialogOptions::addButton(const QString &label, QPlatformDialogHelper::ButtonRole role,
- void *buttonImpl)
+ void *buttonImpl, int buttonId)
{
- const CustomButton b(d->nextCustomButtonId++, label, role, buttonImpl);
+ const CustomButton b(buttonId ? buttonId : d->nextCustomButtonId++, label, role, buttonImpl);
d->customButtons.append(b);
return b.id;
}
@@ -903,6 +905,11 @@ const QList<QMessageDialogOptions::CustomButton> &QMessageDialogOptions::customB
return d->customButtons;
}
+void QMessageDialogOptions::clearCustomButtons()
+{
+ d->customButtons.clear();
+}
+
const QMessageDialogOptions::CustomButton *QMessageDialogOptions::customButton(int id)
{
const int i = int(d->customButtons.indexOf(CustomButton(id)));
@@ -925,6 +932,26 @@ Qt::CheckState QMessageDialogOptions::checkBoxState() const
return d->checkBoxState;
}
+void QMessageDialogOptions::setDefaultButton(int id)
+{
+ d->defaultButtonId = id;
+}
+
+int QMessageDialogOptions::defaultButton() const
+{
+ return d->defaultButtonId;
+}
+
+void QMessageDialogOptions::setEscapeButton(int id)
+{
+ d->escapeButtonId = id;
+}
+
+int QMessageDialogOptions::escapeButton() const
+{
+ return d->escapeButtonId;
+}
+
void QMessageDialogOptions::setOption(QMessageDialogOptions::Option option, bool on)
{
if (!(d->options & option) != !on)
diff --git a/src/gui/kernel/qplatformdialoghelper.h b/src/gui/kernel/qplatformdialoghelper.h
index 150a2333c1..0569a7e2f9 100644
--- a/src/gui/kernel/qplatformdialoghelper.h
+++ b/src/gui/kernel/qplatformdialoghelper.h
@@ -405,7 +405,9 @@ protected:
public:
// Keep in sync with QMessageBox Option
- enum class Option : quint8 { DontUseNativeDialog = 0x00000001 };
+ enum class Option {
+ DontUseNativeDialog = 0x00000001,
+ };
Q_DECLARE_FLAGS(Options, Option);
Q_FLAG(Options);
@@ -457,15 +459,22 @@ public:
};
int addButton(const QString &label, QPlatformDialogHelper::ButtonRole role,
- void *buttonImpl = nullptr);
+ void *buttonImpl = nullptr, int buttonId = 0);
void removeButton(int id);
const QList<CustomButton> &customButtons();
const CustomButton *customButton(int id);
+ void clearCustomButtons();
void setCheckBox(const QString &label, Qt::CheckState state);
QString checkBoxLabel() const;
Qt::CheckState checkBoxState() const;
+ void setEscapeButton(int id);
+ int escapeButton() const;
+
+ void setDefaultButton(int id);
+ int defaultButton() const;
+
private:
QMessageDialogOptionsPrivate *d;
};
diff --git a/src/gui/kernel/qplatforminputcontext.cpp b/src/gui/kernel/qplatforminputcontext.cpp
index 985cefa12c..9d3ee4acb6 100644
--- a/src/gui/kernel/qplatforminputcontext.cpp
+++ b/src/gui/kernel/qplatforminputcontext.cpp
@@ -47,6 +47,11 @@ QT_BEGIN_NAMESPACE
QPlatformInputContext::QPlatformInputContext()
: QObject(*(new QPlatformInputContextPrivate))
{
+ // Delay initialization of cached input direction
+ // until super class has finished constructing.
+ QMetaObject::invokeMethod(this, [this]{
+ m_inputDirection = inputDirection();
+ }, Qt::QueuedConnection);
}
/*!
@@ -192,22 +197,29 @@ void QPlatformInputContext::emitInputPanelVisibleChanged()
QLocale QPlatformInputContext::locale() const
{
- return qt_keymapper_private()->keyboardInputLocale;
+ return QLocale::system();
}
void QPlatformInputContext::emitLocaleChanged()
{
emit QGuiApplication::inputMethod()->localeChanged();
+
+ // Changing the locale might have updated the input direction
+ emitInputDirectionChanged(inputDirection());
}
Qt::LayoutDirection QPlatformInputContext::inputDirection() const
{
- return qt_keymapper_private()->keyboardInputDirection;
+ return locale().textDirection();
}
void QPlatformInputContext::emitInputDirectionChanged(Qt::LayoutDirection newDirection)
{
+ if (newDirection == m_inputDirection)
+ return;
+
emit QGuiApplication::inputMethod()->inputDirectionChanged(newDirection);
+ m_inputDirection = newDirection;
}
/*!
diff --git a/src/gui/kernel/qplatforminputcontext.h b/src/gui/kernel/qplatforminputcontext.h
index 05d7497f9c..481f97a065 100644
--- a/src/gui/kernel/qplatforminputcontext.h
+++ b/src/gui/kernel/qplatforminputcontext.h
@@ -72,6 +72,8 @@ private:
friend class QGuiApplication;
friend class QGuiApplicationPrivate;
friend class QInputMethod;
+
+ Qt::LayoutDirection m_inputDirection;
};
QT_END_NAMESPACE
diff --git a/src/gui/kernel/qplatforminputcontextfactory.cpp b/src/gui/kernel/qplatforminputcontextfactory.cpp
index 7074de56af..933d990f7c 100644
--- a/src/gui/kernel/qplatforminputcontextfactory.cpp
+++ b/src/gui/kernel/qplatforminputcontextfactory.cpp
@@ -28,10 +28,33 @@ QStringList QPlatformInputContextFactory::keys()
#endif
}
-QString QPlatformInputContextFactory::requested()
+QStringList QPlatformInputContextFactory::requested()
{
- QByteArray env = qgetenv("QT_IM_MODULE");
- return env.isNull() ? QString() : QString::fromLocal8Bit(env);
+ QStringList imList;
+ QByteArray env = qgetenv("QT_IM_MODULES");
+
+ if (!env.isEmpty())
+ imList = QString::fromLocal8Bit(env).split(QChar::fromLatin1(';'), Qt::SkipEmptyParts);
+
+ if (!imList.isEmpty())
+ return imList;
+
+ env = qgetenv("QT_IM_MODULE");
+ if (!env.isEmpty())
+ imList = {QString::fromLocal8Bit(env)};
+
+ return imList;
+}
+
+QPlatformInputContext *QPlatformInputContextFactory::create(const QStringList& keys)
+{
+ for (const QString &key : keys) {
+ auto plugin = create(key);
+ if (plugin)
+ return plugin;
+ }
+
+ return nullptr;
}
QPlatformInputContext *QPlatformInputContextFactory::create(const QString& key)
diff --git a/src/gui/kernel/qplatforminputcontextfactory_p.h b/src/gui/kernel/qplatforminputcontextfactory_p.h
index 4968a51a8b..5f5881c508 100644
--- a/src/gui/kernel/qplatforminputcontextfactory_p.h
+++ b/src/gui/kernel/qplatforminputcontextfactory_p.h
@@ -27,7 +27,8 @@ class Q_GUI_EXPORT QPlatformInputContextFactory
{
public:
static QStringList keys();
- static QString requested();
+ static QStringList requested();
+ static QPlatformInputContext *create(const QStringList &keys);
static QPlatformInputContext *create(const QString &key);
static QPlatformInputContext *create();
};
diff --git a/src/gui/kernel/qplatformintegration.cpp b/src/gui/kernel/qplatformintegration.cpp
index b7117b121d..130b479c64 100644
--- a/src/gui/kernel/qplatformintegration.cpp
+++ b/src/gui/kernel/qplatformintegration.cpp
@@ -6,6 +6,7 @@
#include <qpa/qplatformfontdatabase.h>
#include <qpa/qplatformclipboard.h>
#include <qpa/qplatformaccessibility.h>
+#include <qpa/qplatformkeymapper.h>
#include <qpa/qplatformtheme.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qpixmap_raster_p.h>
@@ -228,6 +229,11 @@ QPlatformServices *QPlatformIntegration::services() const
\value ScreenWindowGrabbing The platform supports grabbing window on screen.
On Wayland, this capability can be reported as \c false. The default implementation
of hasCapability() returns \c true.
+
+ \value BackingStoreStaticContents The platform backingstore supports static contents.
+ On resize of the backingstore the static contents region is provided, and the backing
+ store is expected to propagate the static content to the resized backing store, without
+ clients needing to repaint the static content region.
*/
/*!
@@ -342,6 +348,21 @@ QPlatformInputContext *QPlatformIntegration::inputContext() const
return nullptr;
}
+/*!
+ Accessor for the platform integration's key mapper.
+
+ Default implementation returns a default QPlatformKeyMapper.
+
+ \sa QPlatformKeyMapper
+*/
+QPlatformKeyMapper *QPlatformIntegration::keyMapper() const
+{
+ static QPlatformKeyMapper *keyMapper = nullptr;
+ if (!keyMapper)
+ keyMapper = new QPlatformKeyMapper;
+ return keyMapper;
+}
+
#if QT_CONFIG(accessibility)
/*!
diff --git a/src/gui/kernel/qplatformintegration.h b/src/gui/kernel/qplatformintegration.h
index 393845012a..a18ae821c7 100644
--- a/src/gui/kernel/qplatformintegration.h
+++ b/src/gui/kernel/qplatformintegration.h
@@ -33,6 +33,7 @@ class QPlatformOpenGLContext;
class QGuiGLFormat;
class QAbstractEventDispatcher;
class QPlatformInputContext;
+class QPlatformKeyMapper;
class QPlatformAccessibility;
class QPlatformTheme;
class QPlatformDialogHelper;
@@ -98,7 +99,8 @@ public:
MaximizeUsingFullscreenGeometry,
PaintEvents,
RhiBasedRendering,
- ScreenWindowGrabbing // whether QScreen::grabWindow() is supported
+ ScreenWindowGrabbing, // whether QScreen::grabWindow() is supported
+ BackingStoreStaticContents
};
virtual ~QPlatformIntegration() { }
@@ -171,8 +173,12 @@ public:
virtual QVariant styleHint(StyleHint hint) const;
virtual Qt::WindowState defaultWindowState(Qt::WindowFlags) const;
+protected:
virtual Qt::KeyboardModifiers queryKeyboardModifiers() const;
virtual QList<int> possibleKeys(const QKeyEvent *) const;
+ friend class QPlatformKeyMapper;
+public:
+ virtual QPlatformKeyMapper *keyMapper() const;
virtual QStringList themeNames() const;
virtual QPlatformTheme *createPlatformTheme(const QString &name) const;
diff --git a/src/gui/kernel/qplatformkeymapper.cpp b/src/gui/kernel/qplatformkeymapper.cpp
new file mode 100644
index 0000000000..f54e4ff379
--- /dev/null
+++ b/src/gui/kernel/qplatformkeymapper.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qplatformkeymapper.h"
+
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper")
+
+QPlatformKeyMapper::~QPlatformKeyMapper()
+{
+}
+
+/*
+ Should return a list of possible key combinations for the given key event.
+
+ For example, given a US English keyboard layout, the key event Shift+5
+ can represent both a "Shift+5" key combination, as well as just "%".
+*/
+QList<QKeyCombination> QPlatformKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
+{
+ auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ QList<int> possibleKeys = platformIntegration->possibleKeys(event);
+ QList<QKeyCombination> combinations;
+ for (int key : possibleKeys)
+ combinations << QKeyCombination::fromCombined(key);
+ return combinations;
+}
+
+Qt::KeyboardModifiers QPlatformKeyMapper::queryKeyboardModifiers() const
+{
+ return QGuiApplicationPrivate::platformIntegration()->queryKeyboardModifiers();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/kernel/qplatformkeymapper.h b/src/gui/kernel/qplatformkeymapper.h
new file mode 100644
index 0000000000..fb5b0cdb8b
--- /dev/null
+++ b/src/gui/kernel/qplatformkeymapper.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QPLATFORMKEYMAPPER_P
+#define QPLATFORMKEYMAPPER_P
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is part of the QPA API and is not meant to be used
+// in applications. Usage of this API may make your code
+// source and binary incompatible with future versions of Qt.
+//
+
+#include <QtGui/qtguiglobal.h>
+#include <QtCore/qloggingcategory.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcQpaKeyMapper, Q_GUI_EXPORT)
+
+class QKeyEvent;
+
+class Q_GUI_EXPORT QPlatformKeyMapper
+{
+public:
+ virtual ~QPlatformKeyMapper();
+
+ virtual QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const;
+ virtual Qt::KeyboardModifiers queryKeyboardModifiers() const;
+};
+
+QT_END_NAMESPACE
+
+#endif // QPLATFORMKEYMAPPER_P
diff --git a/src/gui/kernel/qplatformscreen.h b/src/gui/kernel/qplatformscreen.h
index de704f4635..a547a635e9 100644
--- a/src/gui/kernel/qplatformscreen.h
+++ b/src/gui/kernel/qplatformscreen.h
@@ -42,6 +42,7 @@ typedef QPair<qreal, qreal> QDpi;
class Q_GUI_EXPORT QPlatformScreen
{
+ Q_GADGET
Q_DECLARE_PRIVATE(QPlatformScreen)
public:
diff --git a/src/gui/kernel/qplatformscreen_p.h b/src/gui/kernel/qplatformscreen_p.h
index e871fc16c8..345a90845c 100644
--- a/src/gui/kernel/qplatformscreen_p.h
+++ b/src/gui/kernel/qplatformscreen_p.h
@@ -20,14 +20,6 @@
#include <QtCore/qpointer.h>
#include <QtCore/qnativeinterface.h>
-#if defined(Q_OS_WIN32)
-#include <qwindowdefs_win.h>
-#endif
-
-#if defined(Q_OS_UNIX)
-struct wl_output;
-#endif
-
QT_BEGIN_NAMESPACE
class QScreen;
@@ -75,31 +67,6 @@ struct Q_GUI_EXPORT QWebOSScreen
virtual void addFlipListener(void (*callback)()) = 0;
};
#endif
-
-#if defined(Q_OS_WIN32) || defined(Q_QDOC)
-struct Q_GUI_EXPORT QWindowsScreen
-{
- QT_DECLARE_NATIVE_INTERFACE(QWindowsScreen, 1, QScreen)
- virtual HMONITOR handle() const = 0;
-};
-#endif
-
-#if defined(Q_OS_UNIX) || defined(Q_CLANG_QDOC)
-struct Q_GUI_EXPORT QWaylandScreen
-{
- QT_DECLARE_NATIVE_INTERFACE(QWaylandScreen, 1, QScreen)
- virtual wl_output *output() const = 0;
-};
-#endif
-
-#if defined(Q_OS_ANDROID) || defined(Q_QDOC)
-struct Q_GUI_EXPORT QAndroidScreen
-{
- QT_DECLARE_NATIVE_INTERFACE(QAndroidScreen, 1, QScreen)
- virtual int displayId() const = 0;
-};
-#endif
-
} // QNativeInterface::Private
QT_END_NAMESPACE
diff --git a/src/gui/kernel/qplatformservices.cpp b/src/gui/kernel/qplatformservices.cpp
index 1c8062389d..7ff2a6c2f8 100644
--- a/src/gui/kernel/qplatformservices.cpp
+++ b/src/gui/kernel/qplatformservices.cpp
@@ -75,3 +75,5 @@ bool QPlatformServices::hasCapability(Capability capability) const
}
QT_END_NAMESPACE
+
+#include "moc_qplatformservices.cpp"
diff --git a/src/gui/kernel/qplatformsystemtrayicon.h b/src/gui/kernel/qplatformsystemtrayicon.h
index c2c80f9334..76a7ef03d9 100644
--- a/src/gui/kernel/qplatformsystemtrayicon.h
+++ b/src/gui/kernel/qplatformsystemtrayicon.h
@@ -6,6 +6,7 @@
#define QPLATFORMSYSTEMTRAYICON_H
#include <QtGui/qtguiglobal.h>
+#include <qpa/qplatformscreen.h>
#include "QtCore/qobject.h"
#ifndef QT_NO_SYSTEMTRAYICON
@@ -13,7 +14,6 @@
QT_BEGIN_NAMESPACE
class QPlatformMenu;
-class QPlatformScreen;
class QIcon;
class QString;
class QRect;
@@ -21,7 +21,6 @@ class QRect;
class Q_GUI_EXPORT QPlatformSystemTrayIcon : public QObject
{
Q_OBJECT
- Q_MOC_INCLUDE(<qpa/qplatformscreen.h>)
public:
enum ActivationReason {
Unknown,
diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp
index 938a0dab72..3d1319615e 100644
--- a/src/gui/kernel/qplatformtheme.cpp
+++ b/src/gui/kernel/qplatformtheme.cpp
@@ -325,7 +325,7 @@ const QKeyBinding QPlatformThemePrivate::keyBindings[] = {
{QKeySequence::InsertLineSeparator, 0, Qt::SHIFT | Qt::Key_Enter, KB_All},
{QKeySequence::InsertLineSeparator, 0, Qt::SHIFT | Qt::Key_Return, KB_All},
{QKeySequence::InsertLineSeparator, 0, Qt::META | Qt::Key_O, KB_Mac},
- {QKeySequence::SaveAs, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_S, KB_Gnome | KB_Mac},
+ {QKeySequence::SaveAs, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_S, KB_All},
{QKeySequence::Preferences, 0, Qt::CTRL | Qt::Key_Comma, KB_Mac},
{QKeySequence::Quit, 0, Qt::CTRL | Qt::Key_Q, KB_X11 | KB_Gnome | KB_KDE | KB_Mac},
{QKeySequence::FullScreen, 1, Qt::META | Qt::CTRL | Qt::Key_F, KB_Mac},
@@ -335,6 +335,7 @@ const QKeyBinding QPlatformThemePrivate::keyBindings[] = {
{QKeySequence::FullScreen, 1, Qt::Key_F11, KB_Win | KB_KDE},
{QKeySequence::Deselect, 0, Qt::CTRL | Qt::SHIFT | Qt::Key_A, KB_X11},
{QKeySequence::DeleteCompleteLine, 0, Qt::CTRL | Qt::Key_U, KB_X11},
+ {QKeySequence::Backspace, 1, Qt::Key_Backspace, KB_Mac},
{QKeySequence::Backspace, 0, Qt::META | Qt::Key_H, KB_Mac},
{QKeySequence::Cancel, 0, Qt::Key_Escape, KB_All},
{QKeySequence::Cancel, 0, Qt::CTRL | Qt::Key_Period, KB_Mac}
@@ -395,9 +396,9 @@ Q_GUI_EXPORT QPalette qt_fusionPalette()
fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, disabledHighlight);
- fusionPalette.setBrush(QPalette::Active, QPalette::AccentColor, highlight);
- fusionPalette.setBrush(QPalette::Inactive, QPalette::AccentColor, highlight);
- fusionPalette.setBrush(QPalette::Disabled, QPalette::AccentColor, disabledHighlight);
+ fusionPalette.setBrush(QPalette::Active, QPalette::Accent, highlight);
+ fusionPalette.setBrush(QPalette::Inactive, QPalette::Accent, highlight);
+ fusionPalette.setBrush(QPalette::Disabled, QPalette::Accent, disabledHighlight);
fusionPalette.setBrush(QPalette::PlaceholderText, placeholder);
@@ -446,6 +447,11 @@ Qt::ColorScheme QPlatformTheme::colorScheme() const
return Qt::ColorScheme::Unknown;
}
+void QPlatformTheme::requestColorScheme(Qt::ColorScheme scheme)
+{
+ Q_UNUSED(scheme);
+}
+
const QPalette *QPlatformTheme::palette(Palette type) const
{
Q_D(const QPlatformTheme);
@@ -637,7 +643,7 @@ QVariant QPlatformTheme::defaultThemeHint(ThemeHint hint)
case FlickMaximumVelocity:
return QVariant(2500);
case FlickDeceleration:
- return QVariant(5000);
+ return QVariant(1500);
case MenuBarFocusOnAltPressRelease:
return false;
case MouseCursorTheme:
diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h
index c0193947b9..d007a19675 100644
--- a/src/gui/kernel/qplatformtheme.h
+++ b/src/gui/kernel/qplatformtheme.h
@@ -295,6 +295,7 @@ public:
#endif
virtual Qt::ColorScheme colorScheme() const;
+ virtual void requestColorScheme(Qt::ColorScheme scheme);
virtual const QPalette *palette(Palette type = SystemPalette) const;
diff --git a/src/gui/kernel/qplatformwindow.cpp b/src/gui/kernel/qplatformwindow.cpp
index e2c9fc4141..5c0ae2ee2a 100644
--- a/src/gui/kernel/qplatformwindow.cpp
+++ b/src/gui/kernel/qplatformwindow.cpp
@@ -370,18 +370,18 @@ void QPlatformWindow::setMask(const QRegion &region)
Reimplement to let Qt be able to request activation/focus for a window
Some window systems will probably not have callbacks for this functionality,
- and then calling QWindowSystemInterface::handleWindowActivated(QWindow *w)
+ and then calling QWindowSystemInterface::handleFocusWindowChanged(QWindow *w)
would be sufficient.
If the window system has some event handling/callbacks then call
- QWindowSystemInterface::handleWindowActivated(QWindow *w) when the window system
+ QWindowSystemInterface::handleFocusWindowChanged(QWindow *w) when the window system
gives the notification.
- Default implementation calls QWindowSystem::handleWindowActivated(QWindow *w)
+ Default implementation calls QWindowSystem::handleFocusWindowChanged(QWindow *w)
*/
void QPlatformWindow::requestActivateWindow()
{
- QWindowSystemInterface::handleWindowActivated(window());
+ QWindowSystemInterface::handleFocusWindowChanged(window());
}
/*!
@@ -778,6 +778,15 @@ void QPlatformWindow::deliverUpdateRequest()
QWindow *w = window();
QWindowPrivate *wp = qt_window_private(w);
+
+ // We expect that the platform plugins send DevicePixelRatioChange events.
+ // As a fail-safe make a final check here to make sure the cached DPR value is
+ // always up to date before delivering the update request.
+ if (wp->updateDevicePixelRatio()) {
+ qWarning() << "The cached device pixel ratio value was stale on window update. "
+ << "Please file a QTBUG which explains how to reproduce.";
+ }
+
wp->updateRequestPending = false;
QEvent request(QEvent::UpdateRequest);
QCoreApplication::sendEvent(w, &request);
diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h
index a1c7b75468..2bbdfd5bf9 100644
--- a/src/gui/kernel/qplatformwindow_p.h
+++ b/src/gui/kernel/qplatformwindow_p.h
@@ -19,9 +19,11 @@
#include <QtCore/qbasictimer.h>
#include <QtCore/qrect.h>
#include <QtCore/qnativeinterface.h>
+#include <QtGui/qwindow.h>
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
#include <any>
+#include <QtCore/qobject.h>
struct wl_surface;
#endif
@@ -104,7 +106,7 @@ struct Q_GUI_EXPORT QWindowsWindow
};
#endif // Q_OS_WIN
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
struct Q_GUI_EXPORT QWaylandWindow : public QObject
{
Q_OBJECT
@@ -124,6 +126,8 @@ public:
Q_SIGNALS:
void surfaceCreated();
void surfaceDestroyed();
+ void surfaceRoleCreated();
+ void surfaceRoleDestroyed();
void xdgActivationTokenCreated(const QString &token);
protected:
diff --git a/src/gui/kernel/qpointingdevice.cpp b/src/gui/kernel/qpointingdevice.cpp
index 3857d454d8..c4c1e5fd5c 100644
--- a/src/gui/kernel/qpointingdevice.cpp
+++ b/src/gui/kernel/qpointingdevice.cpp
@@ -708,7 +708,7 @@ QDebug operator<<(QDebug debug, const QPointingDevice *device)
if (device) {
debug << '"' << device->name() << "\" ";
QtDebugUtils::formatQEnum(debug, device->type());
- debug << " id=" << Qt::hex << device->systemId() << Qt::dec;
+ debug << " id=" << device->systemId();
if (!device->seatName().isEmpty())
debug << " seat=" << device->seatName();
if (device->pointerType() != QPointingDevice::PointerType::Generic) {
diff --git a/src/gui/kernel/qpointingdevice_p.h b/src/gui/kernel/qpointingdevice_p.h
index 403a54dc4f..b2f0574e9b 100644
--- a/src/gui/kernel/qpointingdevice_p.h
+++ b/src/gui/kernel/qpointingdevice_p.h
@@ -20,6 +20,8 @@
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qtguiglobal_p.h>
#include <QtGui/private/qinputdevice_p.h>
+
+#include <QtCore/qpointer.h>
#include <QtCore/private/qflatmap_p.h>
QT_BEGIN_NAMESPACE
diff --git a/src/gui/kernel/qrasterwindow.cpp b/src/gui/kernel/qrasterwindow.cpp
index 19d84c601d..f292344ca1 100644
--- a/src/gui/kernel/qrasterwindow.cpp
+++ b/src/gui/kernel/qrasterwindow.cpp
@@ -34,14 +34,20 @@ class QRasterWindowPrivate : public QPaintDeviceWindowPrivate
{
Q_DECLARE_PUBLIC(QRasterWindow)
public:
+ void handleResizeEvent() override
+ {
+ Q_Q(QRasterWindow);
+ if (backingstore->size() != q->size())
+ markWindowAsDirty();
+ }
+
void beginPaint(const QRegion &region) override
{
Q_Q(QRasterWindow);
const QSize size = q->size();
- if (backingstore->size() != size) {
+ if (backingstore->size() != size)
backingstore->resize(size);
- markWindowAsDirty();
- }
+
backingstore->beginPaint(region);
}
@@ -102,6 +108,10 @@ QPaintDevice *QRasterWindow::redirected(QPoint *) const
return d->backingstore->paintDevice();
}
+void QRasterWindow::resizeEvent(QResizeEvent *)
+{
+}
+
QT_END_NAMESPACE
#include "moc_qrasterwindow.cpp"
diff --git a/src/gui/kernel/qrasterwindow.h b/src/gui/kernel/qrasterwindow.h
index 05d71e1655..986bf6b511 100644
--- a/src/gui/kernel/qrasterwindow.h
+++ b/src/gui/kernel/qrasterwindow.h
@@ -23,6 +23,7 @@ public:
protected:
int metric(PaintDeviceMetric metric) const override;
QPaintDevice *redirected(QPoint *) const override;
+ void resizeEvent(QResizeEvent *event) override;
private:
Q_DISABLE_COPY(QRasterWindow)
diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp
index d598e4fc54..83641e7676 100644
--- a/src/gui/kernel/qscreen.cpp
+++ b/src/gui/kernel/qscreen.cpp
@@ -461,8 +461,8 @@ Qt::ScreenOrientation QScreen::orientation() const
\property QScreen::refreshRate
\brief the approximate vertical refresh rate of the screen in Hz
- \warning Avoid using the screen's refresh rate to drive animations
- via a timer such as QTimer. Instead use QWindow::requestUpdate().
+ \warning Avoid using the screen's refresh rate to drive animations via a
+ timer such as QChronoTimer. Instead use QWindow::requestUpdate().
\sa QWindow::requestUpdate()
*/
@@ -703,8 +703,23 @@ QPixmap QScreen::grabWindow(WId window, int x, int y, int width, int height)
result.setDevicePixelRatio(result.devicePixelRatio() * factor);
return result;
}
+
+/*!
+ \fn template <typename QNativeInterface> QNativeInterface *QScreen::nativeInterface() const
+
+ Returns a native interface of the given type for the screen.
+
+ This function provides access to platform specific functionality
+ of QScreen, as defined in the QNativeInterface namespace:
+
+ \annotatedlist native-interfaces-qscreen
+
+ If the requested interface is not available a \nullptr is returned.
+ */
+
void *QScreen::resolveInterface(const char *name, int revision) const
{
+ using namespace QNativeInterface;
using namespace QNativeInterface::Private;
auto *platformScreen = handle();
@@ -732,7 +747,7 @@ void *QScreen::resolveInterface(const char *name, int revision) const
QT_NATIVE_INTERFACE_RETURN_IF(QAndroidScreen, platformScreen);
#endif
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
QT_NATIVE_INTERFACE_RETURN_IF(QWaylandScreen, platformScreen);
#endif
diff --git a/src/gui/kernel/qscreen.h b/src/gui/kernel/qscreen.h
index d2fc74fbda..9442e7525b 100644
--- a/src/gui/kernel/qscreen.h
+++ b/src/gui/kernel/qscreen.h
@@ -147,5 +147,7 @@ Q_GUI_EXPORT QDebug operator<<(QDebug, const QScreen *);
QT_END_NAMESPACE
+#include <QtGui/qscreen_platform.h>
+
#endif // QSCREEN_H
diff --git a/src/gui/kernel/qscreen_platform.h b/src/gui/kernel/qscreen_platform.h
new file mode 100644
index 0000000000..6f40e9273f
--- /dev/null
+++ b/src/gui/kernel/qscreen_platform.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSCREEN_PLATFORM_H
+#define QSCREEN_PLATFORM_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is part of the native interface APIs. Usage of
+// this API may make your code source and binary incompatible
+// with future versions of Qt.
+//
+
+#include <QtGui/qtguiglobal.h>
+
+#include <QtCore/qnativeinterface.h>
+#include <QtGui/qguiapplication.h>
+
+#if defined(Q_OS_WIN32)
+#include <QtGui/qwindowdefs_win.h>
+#endif
+
+#if QT_CONFIG(wayland)
+struct wl_output;
+#endif
+
+QT_BEGIN_NAMESPACE
+
+namespace QNativeInterface {
+
+#if defined(Q_OS_WIN32) || defined(Q_QDOC)
+struct Q_GUI_EXPORT QWindowsScreen
+{
+ QT_DECLARE_NATIVE_INTERFACE(QWindowsScreen, 1, QScreen)
+ virtual HMONITOR handle() const = 0;
+};
+#endif
+
+#if QT_CONFIG(wayland) || defined(Q_QDOC)
+struct Q_GUI_EXPORT QWaylandScreen
+{
+ QT_DECLARE_NATIVE_INTERFACE(QWaylandScreen, 1, QScreen)
+ virtual wl_output *output() const = 0;
+};
+#endif
+
+#if defined(Q_OS_ANDROID) || defined(Q_QDOC)
+struct Q_GUI_EXPORT QAndroidScreen
+{
+ QT_DECLARE_NATIVE_INTERFACE(QAndroidScreen, 1, QScreen)
+ virtual int displayId() const = 0;
+};
+#endif
+
+} // namespace QNativeInterface
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/kernel/qsessionmanager.cpp b/src/gui/kernel/qsessionmanager.cpp
index 705866f903..0a1ff95656 100644
--- a/src/gui/kernel/qsessionmanager.cpp
+++ b/src/gui/kernel/qsessionmanager.cpp
@@ -281,10 +281,6 @@ void QSessionManager::setRestartCommand(const QStringList &command)
/*!
Returns the currently set restart command.
- To iterate over the list, you can use the \l foreach pseudo-keyword:
-
- \snippet code/src_gui_kernel_qguiapplication.cpp 3
-
\sa setRestartCommand(), restartHint()
*/
QStringList QSessionManager::restartCommand() const
@@ -307,10 +303,6 @@ void QSessionManager::setDiscardCommand(const QStringList &command)
/*!
Returns the currently set discard command.
- To iterate over the list, you can use the \l foreach pseudo-keyword:
-
- \snippet code/src_gui_kernel_qguiapplication.cpp 4
-
\sa setDiscardCommand(), restartCommand(), setRestartCommand()
*/
QStringList QSessionManager::discardCommand() const
diff --git a/src/gui/kernel/qsessionmanager_p.h b/src/gui/kernel/qsessionmanager_p.h
index 60e20b1747..c98f7a5a1d 100644
--- a/src/gui/kernel/qsessionmanager_p.h
+++ b/src/gui/kernel/qsessionmanager_p.h
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
class QPlatformSessionManager;
-class QSessionManagerPrivate : public QObjectPrivate
+class Q_GUI_EXPORT QSessionManagerPrivate : public QObjectPrivate
{
public:
QSessionManagerPrivate(const QString &id,
diff --git a/src/gui/kernel/qshortcut.cpp b/src/gui/kernel/qshortcut.cpp
index b10d96237e..3f6822cb03 100644
--- a/src/gui/kernel/qshortcut.cpp
+++ b/src/gui/kernel/qshortcut.cpp
@@ -96,7 +96,7 @@ QT_BEGIN_NAMESPACE
\sa activated()
*/
-static bool simpleContextMatcher(QObject *object, Qt::ShortcutContext context)
+bool QShortcutPrivate::simpleContextMatcher(QObject *object, Qt::ShortcutContext context)
{
auto guiShortcut = qobject_cast<QShortcut *>(object);
if (QGuiApplication::applicationState() != Qt::ApplicationActive || guiShortcut == nullptr)
diff --git a/src/gui/kernel/qshortcut_p.h b/src/gui/kernel/qshortcut_p.h
index a99cca27c1..8ff833d477 100644
--- a/src/gui/kernel/qshortcut_p.h
+++ b/src/gui/kernel/qshortcut_p.h
@@ -43,6 +43,8 @@ public:
virtual QShortcutMap::ContextMatcher contextMatcher() const;
virtual bool handleWhatsThis() { return false; }
+ static bool simpleContextMatcher(QObject *object, Qt::ShortcutContext context);
+
QList<QKeySequence> sc_sequences;
QString sc_whatsthis;
Qt::ShortcutContext sc_context = Qt::WindowShortcut;
diff --git a/src/gui/kernel/qshortcutmap.cpp b/src/gui/kernel/qshortcutmap.cpp
index 1849419372..800e703ac2 100644
--- a/src/gui/kernel/qshortcutmap.cpp
+++ b/src/gui/kernel/qshortcutmap.cpp
@@ -29,23 +29,23 @@ Q_LOGGING_CATEGORY(lcShortcutMap, "qt.gui.shortcutmap")
struct QShortcutEntry
{
QShortcutEntry()
- : keyseq(0), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
+ : keySequence(0), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
{}
QShortcutEntry(const QKeySequence &k)
- : keyseq(k), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
+ : keySequence(k), context(Qt::WindowShortcut), enabled(false), autorepeat(1), id(0), owner(nullptr), contextMatcher(nullptr)
{}
QShortcutEntry(QObject *o, const QKeySequence &k, Qt::ShortcutContext c, int i, bool a, QShortcutMap::ContextMatcher m)
- : keyseq(k), context(c), enabled(true), autorepeat(a), id(i), owner(o), contextMatcher(m)
+ : keySequence(k), context(c), enabled(true), autorepeat(a), id(i), owner(o), contextMatcher(m)
{}
bool correctContext() const { return contextMatcher(owner, context); }
bool operator<(const QShortcutEntry &f) const
- { return keyseq < f.keyseq; }
+ { return keySequence < f.keySequence; }
- QKeySequence keyseq;
+ QKeySequence keySequence;
Qt::ShortcutContext context;
bool enabled : 1;
bool autorepeat : 1;
@@ -88,7 +88,7 @@ public:
}
QShortcutMap *q_ptr; // Private's parent
- QList<QShortcutEntry> sequences; // All sequences!
+ QList<QShortcutEntry> shortcuts; // All shortcuts!
int currentId; // Global shortcut ID number
int ambigCount; // Index of last enabled ambiguous dispatch
@@ -120,18 +120,18 @@ QShortcutMap::~QShortcutMap()
Adds a shortcut to the global map.
Returns the id of the newly added shortcut.
*/
-int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::ShortcutContext context, ContextMatcher matcher)
+int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &keySequence, Qt::ShortcutContext context, ContextMatcher matcher)
{
Q_ASSERT_X(owner, "QShortcutMap::addShortcut", "All shortcuts need an owner");
- Q_ASSERT_X(!key.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
+ Q_ASSERT_X(!keySequence.isEmpty(), "QShortcutMap::addShortcut", "Cannot add keyless shortcuts to map");
Q_D(QShortcutMap);
- QShortcutEntry newEntry(owner, key, context, --(d->currentId), true, matcher);
- const auto it = std::upper_bound(d->sequences.begin(), d->sequences.end(), newEntry);
- d->sequences.insert(it, newEntry); // Insert sorted
+ QShortcutEntry newEntry(owner, keySequence, context, --(d->currentId), true, matcher);
+ const auto it = std::upper_bound(d->shortcuts.begin(), d->shortcuts.end(), newEntry);
+ d->shortcuts.insert(it, newEntry); // Insert sorted
qCDebug(lcShortcutMap).nospace()
<< "QShortcutMap::addShortcut(" << owner << ", "
- << key << ", " << context << ") added shortcut with ID " << d->currentId;
+ << keySequence << ", " << context << ") added shortcut with ID " << d->currentId;
return d->currentId;
}
@@ -144,36 +144,36 @@ int QShortcutMap::addShortcut(QObject *owner, const QKeySequence &key, Qt::Short
Returns the number of sequences removed from the map.
*/
-int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key)
+int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &keySequence)
{
Q_D(QShortcutMap);
int itemsRemoved = 0;
bool allOwners = (owner == nullptr);
- bool allKeys = key.isEmpty();
+ bool allKeys = keySequence.isEmpty();
bool allIds = id == 0;
auto debug = qScopeGuard([&](){
qCDebug(lcShortcutMap).nospace()
<< "QShortcutMap::removeShortcut(" << id << ", " << owner << ", "
- << key << ") removed " << itemsRemoved << " shortcuts(s)";
+ << keySequence << ") removed " << itemsRemoved << " shortcuts(s)";
});
// Special case, remove everything
if (allOwners && allKeys && allIds) {
- itemsRemoved = d->sequences.size();
- d->sequences.clear();
+ itemsRemoved = d->shortcuts.size();
+ d->shortcuts.clear();
return itemsRemoved;
}
- int i = d->sequences.size()-1;
+ int i = d->shortcuts.size()-1;
while (i>=0)
{
- const QShortcutEntry &entry = d->sequences.at(i);
+ const QShortcutEntry &entry = d->shortcuts.at(i);
int entryId = entry.id;
if ((allOwners || entry.owner == owner)
&& (allIds || entry.id == id)
- && (allKeys || entry.keyseq == key)) {
- d->sequences.removeAt(i);
+ && (allKeys || entry.keySequence == keySequence)) {
+ d->shortcuts.removeAt(i);
++itemsRemoved;
}
if (id == entryId)
@@ -191,22 +191,22 @@ int QShortcutMap::removeShortcut(int id, QObject *owner, const QKeySequence &key
are changed.
Returns the number of sequences which are matched in the map.
*/
-int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &key)
+int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const QKeySequence &keySequence)
{
Q_D(QShortcutMap);
int itemsChanged = 0;
bool allOwners = (owner == nullptr);
- bool allKeys = key.isEmpty();
+ bool allKeys = keySequence.isEmpty();
bool allIds = id == 0;
- int i = d->sequences.size()-1;
+ int i = d->shortcuts.size()-1;
while (i>=0)
{
- QShortcutEntry entry = d->sequences.at(i);
+ QShortcutEntry entry = d->shortcuts.at(i);
if ((allOwners || entry.owner == owner)
&& (allIds || entry.id == id)
- && (allKeys || entry.keyseq == key)) {
- d->sequences[i].enabled = enable;
+ && (allKeys || entry.keySequence == keySequence)) {
+ d->shortcuts[i].enabled = enable;
++itemsChanged;
}
if (id == entry.id)
@@ -215,7 +215,7 @@ int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const
}
qCDebug(lcShortcutMap).nospace()
<< "QShortcutMap::setShortcutEnabled(" << enable << ", " << id << ", "
- << owner << ", " << key << ") = " << itemsChanged;
+ << owner << ", " << keySequence << ") = " << itemsChanged;
return itemsChanged;
}
@@ -227,22 +227,22 @@ int QShortcutMap::setShortcutEnabled(bool enable, int id, QObject *owner, const
are changed.
Returns the number of sequences which are matched in the map.
*/
-int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &key)
+int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const QKeySequence &keySequence)
{
Q_D(QShortcutMap);
int itemsChanged = 0;
bool allOwners = (owner == nullptr);
- bool allKeys = key.isEmpty();
+ bool allKeys = keySequence.isEmpty();
bool allIds = id == 0;
- int i = d->sequences.size()-1;
+ int i = d->shortcuts.size()-1;
while (i>=0)
{
- QShortcutEntry entry = d->sequences.at(i);
+ QShortcutEntry entry = d->shortcuts.at(i);
if ((allOwners || entry.owner == owner)
&& (allIds || entry.id == id)
- && (allKeys || entry.keyseq == key)) {
- d->sequences[i].autorepeat = on;
+ && (allKeys || entry.keySequence == keySequence)) {
+ d->shortcuts[i].autorepeat = on;
++itemsChanged;
}
if (id == entry.id)
@@ -251,7 +251,7 @@ int QShortcutMap::setShortcutAutoRepeat(bool on, int id, QObject *owner, const Q
}
qCDebug(lcShortcutMap).nospace()
<< "QShortcutMap::setShortcutAutoRepeat(" << on << ", " << id << ", "
- << owner << ", " << key << ") = " << itemsChanged;
+ << owner << ", " << keySequence << ") = " << itemsChanged;
return itemsChanged;
}
@@ -365,11 +365,12 @@ bool QShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const
{
Q_D(const QShortcutMap);
QShortcutEntry entry(seq); // needed for searching
- const auto itEnd = d->sequences.cend();
- auto it = std::lower_bound(d->sequences.cbegin(), itEnd, entry);
+ const auto itEnd = d->shortcuts.cend();
+ auto it = std::lower_bound(d->shortcuts.cbegin(), itEnd, entry);
for (;it != itEnd; ++it) {
- if (matches(entry.keyseq, (*it).keyseq) == QKeySequence::ExactMatch && (*it).correctContext() && (*it).enabled) {
+ if (entry.keySequence.matches(it->keySequence) == QKeySequence::ExactMatch
+ && (*it).correctContext() && (*it).enabled) {
return true;
}
}
@@ -388,11 +389,11 @@ bool QShortcutMap::hasShortcutForKeySequence(const QKeySequence &seq) const
QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifiers)
{
Q_D(QShortcutMap);
- if (!d->sequences.size())
+ if (!d->shortcuts.size())
return QKeySequence::NoMatch;
createNewSequences(e, d->newEntries, ignoredModifiers);
- qCDebug(lcShortcutMap) << "Possible shortcut key sequences:" << d->newEntries;
+ qCDebug(lcShortcutMap) << "Possible input sequences:" << d->newEntries;
// Should never happen
if (d->newEntries == d->currentSequences) {
@@ -407,29 +408,33 @@ QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifier
bool partialFound = false;
bool identicalDisabledFound = false;
QList<QKeySequence> okEntries;
- int result = QKeySequence::NoMatch;
+ QKeySequence::SequenceMatch result = QKeySequence::NoMatch;
for (int i = d->newEntries.size()-1; i >= 0 ; --i) {
QShortcutEntry entry(d->newEntries.at(i)); // needed for searching
- qCDebug(lcShortcutMap) << "- checking entry" << entry.id << entry.keyseq;
- const auto itEnd = d->sequences.constEnd();
- auto it = std::lower_bound(d->sequences.constBegin(), itEnd, entry);
-
- int oneKSResult = QKeySequence::NoMatch;
- int tempRes = QKeySequence::NoMatch;
- do {
- if (it == itEnd)
+ qCDebug(lcShortcutMap) << "Looking for shortcuts matching" << entry.keySequence;
+
+ QKeySequence::SequenceMatch bestMatchForEntry = QKeySequence::NoMatch;
+
+ const auto itEnd = d->shortcuts.constEnd();
+ auto it = std::lower_bound(d->shortcuts.constBegin(), itEnd, entry);
+ for (; it != itEnd; ++it) {
+ QKeySequence::SequenceMatch match = entry.keySequence.matches(it->keySequence);
+ qCDebug(lcShortcutMap) << " -" << match << "for shortcut" << it->keySequence;
+
+ // If we got a valid match, there might still be more keys to check against,
+ // but if we get no match, we know that there are no more possible matches.
+ if (match == QKeySequence::NoMatch)
break;
- tempRes = matches(entry.keyseq, (*it).keyseq);
- oneKSResult = qMax(oneKSResult, tempRes);
- qCDebug(lcShortcutMap) << " - matches returned" << tempRes << "for" << entry.keyseq << it->keyseq
- << "- correctContext()?" << it->correctContext();
- if (tempRes != QKeySequence::NoMatch && (*it).correctContext()) {
- if (tempRes == QKeySequence::ExactMatch) {
+
+ bestMatchForEntry = qMax(bestMatchForEntry, match);
+
+ if ((*it).correctContext()) {
+ if (match == QKeySequence::ExactMatch) {
if ((*it).enabled)
d->identicals.append(&*it);
else
identicalDisabledFound = true;
- } else if (tempRes == QKeySequence::PartialMatch) {
+ } else if (match == QKeySequence::PartialMatch) {
// We don't need partials, if we have identicals
if (d->identicals.size())
break;
@@ -437,20 +442,18 @@ QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifier
// key events when all partials are disabled!
partialFound |= (*it).enabled;
}
+ } else {
+ qCDebug(lcShortcutMap) << " - But context was not correct";
}
- ++it;
- // If we got a valid match on this run, there might still be more keys to check against,
- // so we'll loop once more. If we get NoMatch, there's guaranteed no more possible
- // matches in the shortcutmap.
- } while (tempRes != QKeySequence::NoMatch);
+ }
// If the type of match improves (ergo, NoMatch->Partial, or Partial->Exact), clear the
// previous list. If this match is equal or better than the last match, append to the list
- if (oneKSResult > result) {
+ if (bestMatchForEntry > result) {
okEntries.clear();
qCDebug(lcShortcutMap) << "Found better match (" << d->newEntries << "), clearing key sequence list";
}
- if (oneKSResult && oneKSResult >= result) {
+ if (bestMatchForEntry && bestMatchForEntry >= result) {
okEntries << d->newEntries.at(i);
qCDebug(lcShortcutMap) << "Added ok key sequence" << d->newEntries;
}
@@ -469,7 +472,7 @@ QKeySequence::SequenceMatch QShortcutMap::find(QKeyEvent *e, int ignoredModifier
if (result != QKeySequence::NoMatch)
d->currentSequences = okEntries;
qCDebug(lcShortcutMap) << "Returning shortcut match == " << result;
- return QKeySequence::SequenceMatch(result);
+ return result;
}
/*! \internal
@@ -490,7 +493,7 @@ void QShortcutMap::clearSequence(QList<QKeySequence> &ksl)
void QShortcutMap::createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl, int ignoredModifiers)
{
Q_D(QShortcutMap);
- QList<int> possibleKeys = QKeyMapper::possibleKeys(e);
+ QList<QKeyCombination> possibleKeys = QKeyMapper::possibleKeys(e);
qCDebug(lcShortcutMap) << "Creating new sequences for" << e
<< "with ignoredModifiers=" << Qt::KeyboardModifiers(ignoredModifiers);
int pkTotal = possibleKeys.size();
@@ -519,46 +522,13 @@ void QShortcutMap::createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl, in
curKsl.setKey(QKeyCombination::fromCombined(0), 2);
curKsl.setKey(QKeyCombination::fromCombined(0), 3);
}
- curKsl.setKey(QKeyCombination::fromCombined(possibleKeys.at(pkNum) & ~ignoredModifiers), index);
+ const int key = possibleKeys.at(pkNum).toCombined();
+ curKsl.setKey(QKeyCombination::fromCombined(key & ~ignoredModifiers), index);
}
}
}
/*! \internal
- Basically the same function as QKeySequence::matches(const QKeySequence &seq) const
- only that is specially handles Key_hyphen as Key_Minus, as people mix these up all the time and
- they conceptually the same.
-*/
-QKeySequence::SequenceMatch QShortcutMap::matches(const QKeySequence &seq1,
- const QKeySequence &seq2) const
-{
- uint userN = seq1.count(),
- seqN = seq2.count();
-
- if (userN > seqN)
- return QKeySequence::NoMatch;
-
- // If equal in length, we have a potential ExactMatch sequence,
- // else we already know it can only be partial.
- QKeySequence::SequenceMatch match = (userN == seqN
- ? QKeySequence::ExactMatch
- : QKeySequence::PartialMatch);
-
- for (uint i = 0; i < userN; ++i) {
- int userKey = seq1[i].toCombined(),
- sequenceKey = seq2[i].toCombined();
- if ((userKey & Qt::Key_unknown) == Qt::Key_hyphen)
- userKey = (userKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
- if ((sequenceKey & Qt::Key_unknown) == Qt::Key_hyphen)
- sequenceKey = (sequenceKey & Qt::KeyboardModifierMask) | Qt::Key_Minus;
- if (userKey != sequenceKey)
- return QKeySequence::NoMatch;
- }
- return match;
-}
-
-
-/*! \internal
Converts keyboard button states into modifier states
*/
int QShortcutMap::translateModifiers(Qt::KeyboardModifiers modifiers)
@@ -593,7 +563,7 @@ void QShortcutMap::dispatchEvent(QKeyEvent *e)
if (!d->identicals.size())
return;
- const QKeySequence &curKey = d->identicals.at(0)->keyseq;
+ const QKeySequence &curKey = d->identicals.at(0)->keySequence;
if (d->prevSequence != curKey) {
d->ambigCount = 0;
d->prevSequence = curKey;
@@ -624,15 +594,15 @@ void QShortcutMap::dispatchEvent(QKeyEvent *e)
if (ambiguousShortcuts.size() > 1) {
qCDebug(lcShortcutMap) << "The following shortcuts are about to be activated ambiguously:";
for (const QShortcutEntry *entry : std::as_const(ambiguousShortcuts))
- qCDebug(lcShortcutMap).nospace() << "- " << entry->keyseq << " (belonging to " << entry->owner << ")";
+ qCDebug(lcShortcutMap).nospace() << "- " << entry->keySequence << " (belonging to " << entry->owner << ")";
}
qCDebug(lcShortcutMap).nospace()
<< "QShortcutMap::dispatchEvent(): Sending QShortcutEvent(\""
- << next->keyseq.toString() << "\", " << next->id << ", "
+ << next->keySequence.toString() << "\", " << next->id << ", "
<< static_cast<bool>(enabledShortcuts>1) << ") to object(" << next->owner << ')';
}
- QShortcutEvent se(next->keyseq, next->id, enabledShortcuts>1);
+ QShortcutEvent se(next->keySequence, next->id, enabledShortcuts > 1);
QCoreApplication::sendEvent(const_cast<QObject *>(next->owner), &se);
}
@@ -640,7 +610,7 @@ QList<QKeySequence> QShortcutMap::keySequences(bool getAll) const
{
Q_D(const QShortcutMap);
QList<QKeySequence> keys;
- for (auto sequence : d->sequences) {
+ for (auto sequence : d->shortcuts) {
bool addSequence = false;
if (sequence.enabled) {
if (getAll || sequence.context == Qt::ApplicationShortcut ||
@@ -669,7 +639,7 @@ QList<QKeySequence> QShortcutMap::keySequences(bool getAll) const
}
}
if (addSequence)
- keys << sequence.keyseq;
+ keys << sequence.keySequence;
}
}
return keys;
@@ -684,8 +654,8 @@ QList<QKeySequence> QShortcutMap::keySequences(bool getAll) const
void QShortcutMap::dumpMap() const
{
Q_D(const QShortcutMap);
- for (int i = 0; i < d->sequences.size(); ++i)
- qDebug().nospace() << &(d->sequences.at(i));
+ for (int i = 0; i < d->shortcuts.size(); ++i)
+ qDebug().nospace() << &(d->shortcuts.at(i));
}
#endif
diff --git a/src/gui/kernel/qshortcutmap_p.h b/src/gui/kernel/qshortcutmap_p.h
index 194738f0ac..26d2b5301c 100644
--- a/src/gui/kernel/qshortcutmap_p.h
+++ b/src/gui/kernel/qshortcutmap_p.h
@@ -62,7 +62,6 @@ private:
void dispatchEvent(QKeyEvent *e);
QKeySequence::SequenceMatch find(QKeyEvent *e, int ignoredModifiers = 0);
- QKeySequence::SequenceMatch matches(const QKeySequence &seq1, const QKeySequence &seq2) const;
QList<const QShortcutEntry *> matches() const;
void createNewSequences(QKeyEvent *e, QList<QKeySequence> &ksl, int ignoredModifiers);
void clearSequence(QList<QKeySequence> &ksl);
diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp
index 5becae76c6..73c6199733 100644
--- a/src/gui/kernel/qstylehints.cpp
+++ b/src/gui/kernel/qstylehints.cpp
@@ -123,8 +123,29 @@ int QStyleHints::touchDoubleTapDistance() const
/*!
\property QStyleHints::colorScheme
- \brief the color scheme of the platform theme.
- \sa Qt::ColorScheme
+ \brief the color scheme used by the application.
+
+ By default, this follows the system's default color scheme (also known as appearance),
+ and changes when the system color scheme changes (e.g. during dusk or dawn).
+ Setting the color scheme to an explicit value will override the system setting and
+ ignore any changes to the system's color scheme. However, doing so is a hint to the
+ system, and overriding the color scheme is not supported on all platforms.
+
+ Resetting this property, or setting it to \l{Qt::ColorScheme::Unknown}, will remove
+ the override and make the application follow the system default again. The property
+ value will change to the color scheme the system currently has.
+
+ When this property changes, Qt will read the system palette and update the default
+ palette, but won't overwrite palette entries that have been explicitly set by the
+ application. When the colorSchemeChange() signal gets emitted, the old palette is
+ still in effect.
+
+ Application-specific colors should be selected to work well with the effective
+ palette, taking the current color scheme into account. To update application-
+ specific colors when the effective palette changes, handle
+ \l{QEvent::}{PaletteChange} or \l{QEvent::}{ApplicationPaletteChange} events.
+
+ \sa Qt::ColorScheme, QGuiApplication::palette(), QEvent::PaletteChange
\since 6.5
*/
Qt::ColorScheme QStyleHints::colorScheme() const
@@ -134,6 +155,30 @@ Qt::ColorScheme QStyleHints::colorScheme() const
}
/*!
+ \since 6.8
+
+ Sets the color scheme used by the application to an explicit \a scheme, or
+ revert to the system's current color scheme if \a scheme is Qt::ColorScheme::Unknown.
+*/
+void QStyleHints::setColorScheme(Qt::ColorScheme scheme)
+{
+ if (!QCoreApplication::instance()) {
+ qWarning("Must construct a QGuiApplication before accessing a platform theme hint.");
+ return;
+ }
+ if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
+ theme->requestColorScheme(scheme);
+}
+
+/*!
+ \fn void QStyleHints::unsetColorScheme()
+ \since 6.8
+
+ Restores the color scheme to the system's current color scheme.
+*/
+
+
+/*!
Sets the \a mousePressAndHoldInterval.
\internal
\sa mousePressAndHoldInterval()
@@ -398,6 +443,40 @@ void QStyleHints::setShowShortcutsInContextMenus(bool s)
}
/*!
+ \property QStyleHints::contextMenuTrigger
+ \since 6.8
+ \brief mouse event used to trigger a context menu event.
+
+ The default on UNIX systems is to show context menu on mouse button press event, while on
+ Windows it is the mouse button release event. This property can be used to override the default
+ platform behavior.
+
+ \note Developers must use this property with great care, as it changes the default interaction
+ mode that their users will expect on the platform that they are running on.
+
+ \sa Qt::ContextMenuTrigger
+*/
+Qt::ContextMenuTrigger QStyleHints::contextMenuTrigger() const
+{
+ Q_D(const QStyleHints);
+ if (d->m_contextMenuTrigger == -1) {
+ return themeableHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool()
+ ? Qt::ContextMenuTrigger::Release
+ : Qt::ContextMenuTrigger::Press;
+ }
+ return Qt::ContextMenuTrigger(d->m_contextMenuTrigger);
+}
+
+void QStyleHints::setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger)
+{
+ Q_D(QStyleHints);
+ const Qt::ContextMenuTrigger currentTrigger = this->contextMenuTrigger();
+ d->m_contextMenuTrigger = int(contextMenuTrigger);
+ if (currentTrigger != contextMenuTrigger)
+ emit contextMenuTriggerChanged(contextMenuTrigger);
+}
+
+/*!
\property QStyleHints::passwordMaskDelay
\brief the time, in milliseconds, a typed letter is displayed unshrouded
in a text input field in password mode.
@@ -595,11 +674,15 @@ int QStyleHints::mouseQuickSelectionThreshold() const
/*!
\internal
- QStyleHintsPrivate::setColorScheme - set a new color scheme.
+ QStyleHintsPrivate::updateColorScheme - set a new color scheme.
+
+ This function is called by the QPA plugin when the system theme changes. This in
+ turn might be the result of an explicit request of a color scheme via setColorScheme.
+
Set \a colorScheme as the new color scheme of the QStyleHints.
The colorSchemeChanged signal will be emitted if present and new color scheme differ.
*/
-void QStyleHintsPrivate::setColorScheme(Qt::ColorScheme colorScheme)
+void QStyleHintsPrivate::updateColorScheme(Qt::ColorScheme colorScheme)
{
if (m_colorScheme == colorScheme)
return;
diff --git a/src/gui/kernel/qstylehints.h b/src/gui/kernel/qstylehints.h
index 969bec4b35..97ef59f3cf 100644
--- a/src/gui/kernel/qstylehints.h
+++ b/src/gui/kernel/qstylehints.h
@@ -36,6 +36,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(bool showIsMaximized READ showIsMaximized STORED false CONSTANT FINAL)
Q_PROPERTY(bool showShortcutsInContextMenus READ showShortcutsInContextMenus
WRITE setShowShortcutsInContextMenus NOTIFY showShortcutsInContextMenusChanged FINAL)
+ Q_PROPERTY(Qt::ContextMenuTrigger contextMenuTrigger READ contextMenuTrigger WRITE
+ setContextMenuTrigger NOTIFY contextMenuTriggerChanged FINAL)
Q_PROPERTY(int startDragDistance READ startDragDistance NOTIFY startDragDistanceChanged FINAL)
Q_PROPERTY(int startDragTime READ startDragTime NOTIFY startDragTimeChanged FINAL)
Q_PROPERTY(int startDragVelocity READ startDragVelocity STORED false CONSTANT FINAL)
@@ -52,7 +54,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(int mouseDoubleClickDistance READ mouseDoubleClickDistance STORED false CONSTANT
FINAL)
Q_PROPERTY(int touchDoubleTapDistance READ touchDoubleTapDistance STORED false CONSTANT FINAL)
- Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme NOTIFY colorSchemeChanged FINAL)
+ Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme WRITE setColorScheme
+ RESET unsetColorScheme NOTIFY colorSchemeChanged FINAL)
public:
void setMouseDoubleClickInterval(int mouseDoubleClickInterval);
@@ -79,6 +82,8 @@ public:
bool showIsMaximized() const;
bool showShortcutsInContextMenus() const;
void setShowShortcutsInContextMenus(bool showShortcutsInContextMenus);
+ Qt::ContextMenuTrigger contextMenuTrigger() const;
+ void setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger);
int passwordMaskDelay() const;
QChar passwordMaskCharacter() const;
qreal fontSmoothingGamma() const;
@@ -94,6 +99,8 @@ public:
void setMouseQuickSelectionThreshold(int threshold);
int mouseQuickSelectionThreshold() const;
Qt::ColorScheme colorScheme() const;
+ void setColorScheme(Qt::ColorScheme scheme);
+ void unsetColorScheme() { setColorScheme(Qt::ColorScheme::Unknown); }
Q_SIGNALS:
void cursorFlashTimeChanged(int cursorFlashTime);
@@ -105,6 +112,7 @@ Q_SIGNALS:
void tabFocusBehaviorChanged(Qt::TabFocusBehavior tabFocusBehavior);
void useHoverEffectsChanged(bool useHoverEffects);
void showShortcutsInContextMenusChanged(bool);
+ void contextMenuTriggerChanged(Qt::ContextMenuTrigger contextMenuTrigger);
void wheelScrollLinesChanged(int scrollLines);
void mouseQuickSelectionThresholdChanged(int threshold);
void colorSchemeChanged(Qt::ColorScheme colorScheme);
diff --git a/src/gui/kernel/qstylehints_p.h b/src/gui/kernel/qstylehints_p.h
index c58386d7a3..497bf95cbf 100644
--- a/src/gui/kernel/qstylehints_p.h
+++ b/src/gui/kernel/qstylehints_p.h
@@ -35,13 +35,14 @@ public:
int m_tabFocusBehavior = -1;
int m_uiEffects = -1;
int m_showShortcutsInContextMenus = -1;
+ int m_contextMenuTrigger = -1;
int m_wheelScrollLines = -1;
int m_mouseQuickSelectionThreshold = -1;
int m_mouseDoubleClickDistance = -1;
int m_touchDoubleTapDistance = -1;
Qt::ColorScheme colorScheme() const { return m_colorScheme; }
- void setColorScheme(Qt::ColorScheme colorScheme);
+ void updateColorScheme(Qt::ColorScheme colorScheme);
static QStyleHintsPrivate *get(QStyleHints *q);
diff --git a/src/gui/kernel/qsurfaceformat.cpp b/src/gui/kernel/qsurfaceformat.cpp
index cc2bbea551..74add6e973 100644
--- a/src/gui/kernel/qsurfaceformat.cpp
+++ b/src/gui/kernel/qsurfaceformat.cpp
@@ -765,7 +765,7 @@ Q_GLOBAL_STATIC(QSurfaceFormat, qt_default_surface_format)
question's own setFormat() function. However, it is often more convenient to
set the format for all windows once at the start of the application. It also
guarantees proper behavior in cases where shared contexts are required,
- because settings the format via this function guarantees that all contexts
+ because setting the format via this function guarantees that all contexts
and surfaces, even the ones created internally by Qt, will use the same
format.
diff --git a/src/gui/kernel/qtestsupport_gui.cpp b/src/gui/kernel/qtestsupport_gui.cpp
index ac58ab449c..869eddce49 100644
--- a/src/gui/kernel/qtestsupport_gui.cpp
+++ b/src/gui/kernel/qtestsupport_gui.cpp
@@ -29,7 +29,7 @@ QT_BEGIN_NAMESPACE
\note Since focus is an exclusive property, \a window may loose its focus to another window at
any time - even after the method has returned \c true.
- \sa qWaitForWindowExposed(), QWindow::isActive()
+ \sa qWaitForWindowExposed(), qWaitForWindowFocused(), QWindow::isActive()
*/
Q_GUI_EXPORT bool QTest::qWaitForWindowActive(QWindow *window, int timeout)
{
@@ -45,6 +45,27 @@ Q_GUI_EXPORT bool QTest::qWaitForWindowActive(QWindow *window, int timeout)
}
/*!
+ \since 6.7
+
+ Returns \c true, if \a window is the focus window within \a timeout. Otherwise returns \c false.
+
+ The method is useful in tests that call QWindow::show() and rely on the window
+ having focus (for receiving keyboard events e.g.) before proceeding.
+
+ \note The method will time out and return \c false if another window prevents \a window from
+ becoming focused.
+
+ \note Since focus is an exclusive property, \a window may loose its focus to another window at
+ any time - even after the method has returned \c true.
+
+ \sa qWaitForWindowExposed(), qWaitForWindowActive(), QGuiApplication::focusWindow()
+*/
+Q_GUI_EXPORT bool QTest::qWaitForWindowFocused(QWindow *window, QDeadlineTimer timeout)
+{
+ return QTest::qWaitFor([&]() { return qGuiApp->focusWindow() == window; }, timeout);
+}
+
+/*!
\since 5.0
Returns \c true, if \a window is exposed within \a timeout milliseconds. Otherwise returns \c false.
diff --git a/src/gui/kernel/qtestsupport_gui.h b/src/gui/kernel/qtestsupport_gui.h
index e93fd52018..e5b2a88455 100644
--- a/src/gui/kernel/qtestsupport_gui.h
+++ b/src/gui/kernel/qtestsupport_gui.h
@@ -23,6 +23,7 @@ Q_GUI_EXPORT bool qt_handleTouchEventv2(QWindow *w, const QPointingDevice *devic
namespace QTest {
[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowActive(QWindow *window, int timeout = 5000);
+[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowFocused(QWindow *widget, QDeadlineTimer timeout = std::chrono::seconds{5});
[[nodiscard]] Q_GUI_EXPORT bool qWaitForWindowExposed(QWindow *window, int timeout = 5000);
Q_GUI_EXPORT QPointingDevice * createTouchDevice(QInputDevice::DeviceType devType = QInputDevice::DeviceType::TouchScreen,
diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp
index b59c32ac34..7c885032c7 100644
--- a/src/gui/kernel/qwindow.cpp
+++ b/src/gui/kernel/qwindow.cpp
@@ -128,7 +128,7 @@ QWindow::QWindow(QScreen *targetScreen)
, QSurface(QSurface::Window)
{
Q_D(QWindow);
- d->init(targetScreen);
+ d->init(nullptr, targetScreen);
}
static QWindow *nonDesktopParent(QWindow *parent)
@@ -169,11 +169,11 @@ QWindow::QWindow(QWindow *parent)
\sa setParent()
*/
QWindow::QWindow(QWindowPrivate &dd, QWindow *parent)
- : QObject(dd, nonDesktopParent(parent))
+ : QObject(dd, nullptr)
, QSurface(QSurface::Window)
{
Q_D(QWindow);
- d->init();
+ d->init(nonDesktopParent(parent));
}
/*!
@@ -183,6 +183,8 @@ QWindow::~QWindow()
{
Q_D(QWindow);
d->destroy();
+ // Decouple from parent before window goes under
+ setParent(nullptr);
QGuiApplicationPrivate::window_list.removeAll(this);
if (!QGuiApplicationPrivate::is_app_closing)
QGuiApplicationPrivate::instance()->modalWindowList.removeOne(this);
@@ -206,10 +208,12 @@ QWindowPrivate::QWindowPrivate()
QWindowPrivate::~QWindowPrivate()
= default;
-void QWindowPrivate::init(QScreen *targetScreen)
+void QWindowPrivate::init(QWindow *parent, QScreen *targetScreen)
{
Q_Q(QWindow);
+ q->QObject::setParent(parent);
+
isWindow = true;
parentWindow = static_cast<QWindow *>(q->QObject::parent());
@@ -227,6 +231,26 @@ void QWindowPrivate::init(QScreen *targetScreen)
requestedFormat = QSurfaceFormat::defaultFormat();
devicePixelRatio = connectScreen->devicePixelRatio();
+
+ QObject::connect(q, &QWindow::screenChanged, q, [q, this](QScreen *){
+ // We may have changed scaling; trigger resize event if needed,
+ // except on Windows, where we send resize events during WM_DPICHANGED
+ // event handling. FIXME: unify DPI change handling across all platforms.
+#ifndef Q_OS_WIN
+ if (q->handle()) {
+ QWindowSystemInterfacePrivate::GeometryChangeEvent gce(q, QHighDpi::fromNativePixels(q->handle()->geometry(), q));
+ QGuiApplicationPrivate::processGeometryChangeEvent(&gce);
+ }
+#else
+ Q_UNUSED(q);
+#endif
+ updateDevicePixelRatio();
+ });
+
+ if (parentWindow) {
+ QChildWindowEvent childAddedEvent(QEvent::ChildWindowAdded, q);
+ QCoreApplication::sendEvent(parentWindow, &childAddedEvent);
+ }
}
/*!
@@ -497,7 +521,9 @@ void QWindowPrivate::setTopLevelScreen(QScreen *newScreen, bool recreate)
}
}
-void QWindowPrivate::create(bool recursive, WId nativeHandle)
+static constexpr auto kForeignWindowId = "_q_foreignWinId";
+
+void QWindowPrivate::create(bool recursive)
{
Q_Q(QWindow);
if (platformWindow)
@@ -511,6 +537,13 @@ void QWindowPrivate::create(bool recursive, WId nativeHandle)
if (q->parent())
q->parent()->create();
+ if (platformWindow) {
+ // Creating the parent window will end up creating any child window
+ // that was already visible, via setVisible. If this applies to us,
+ // we will already have a platform window at this point.
+ return;
+ }
+
// QPlatformWindow will poll geometry() during construction below. Set the
// screen here so that high-dpi scaling will use the correct scale factor.
if (q->isTopLevel()) {
@@ -518,6 +551,8 @@ void QWindowPrivate::create(bool recursive, WId nativeHandle)
setTopLevelScreen(screen, false);
}
+ const WId nativeHandle = q->property(kForeignWindowId).value<WId>();
+
QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
platformWindow = nativeHandle ? platformIntegration->createForeignWindow(q, nativeHandle)
: platformIntegration->createPlatformWindow(q);
@@ -597,7 +632,9 @@ void QWindowPrivate::setMinOrMaxSize(QSize *oldSizeMember, const QSize &size,
// resize window if current size is outside of min and max limits
if (minimumSize.width() <= maximumSize.width()
|| minimumSize.height() <= maximumSize.height()) {
- q->resize(q->geometry().size().expandedTo(minimumSize).boundedTo(maximumSize));
+ const QSize currentSize = q->size();
+ const QSize boundedSize = currentSize.expandedTo(minimumSize).boundedTo(maximumSize);
+ q->resize(boundedSize);
}
}
@@ -687,6 +724,7 @@ void QWindow::create()
Returns the window's platform id.
\note This function will cause the platform window to be created if it is not already.
+ Returns 0, if the platform window creation failed.
For platforms where this id might be useful, the value returned
will uniquely represent the window inside the corresponding screen.
@@ -700,6 +738,9 @@ WId QWindow::winId() const
if (!d->platformWindow)
const_cast<QWindow *>(this)->create();
+ if (!d->platformWindow)
+ return 0;
+
return d->platformWindow->winId();
}
@@ -743,6 +784,10 @@ void QWindow::setParent(QWindow *parent)
return;
}
+ QEvent parentAboutToChangeEvent(QEvent::ParentWindowAboutToChange);
+ QCoreApplication::sendEvent(this, &parentAboutToChangeEvent);
+
+ const auto previousParent = d->parentWindow;
QObject::setParent(parent);
d->parentWindow = parent;
@@ -765,6 +810,19 @@ void QWindow::setParent(QWindow *parent)
}
QGuiApplicationPrivate::updateBlockedStatus(this);
+
+ if (previousParent) {
+ QChildWindowEvent childRemovedEvent(QEvent::ChildWindowRemoved, this);
+ QCoreApplication::sendEvent(previousParent, &childRemovedEvent);
+ }
+
+ if (parent) {
+ QChildWindowEvent childAddedEvent(QEvent::ChildWindowAdded, this);
+ QCoreApplication::sendEvent(parent, &childAddedEvent);
+ }
+
+ QEvent parentChangedEvent(QEvent::ParentWindowChange);
+ QCoreApplication::sendEvent(this, &parentChangedEvent);
}
/*!
@@ -1337,8 +1395,9 @@ qreal QWindow::devicePixelRatio() const
/*
Updates the cached devicePixelRatio value by polling for a new value.
Sends QEvent::DevicePixelRatioChange to the window if the DPR has changed.
+ Returns true if the DPR was changed.
*/
-void QWindowPrivate::updateDevicePixelRatio()
+bool QWindowPrivate::updateDevicePixelRatio()
{
Q_Q(QWindow);
@@ -1349,11 +1408,12 @@ void QWindowPrivate::updateDevicePixelRatio()
platformWindow->devicePixelRatio() * QHighDpiScaling::factor(q) : q->screen()->devicePixelRatio();
if (newDevicePixelRatio == devicePixelRatio)
- return;
+ return false;
devicePixelRatio = newDevicePixelRatio;
QEvent dprChangeEvent(QEvent::DevicePixelRatioChange);
QGuiApplication::sendEvent(q, &dprChangeEvent);
+ return true;
}
Qt::WindowState QWindowPrivate::effectiveState(Qt::WindowStates state)
@@ -1624,20 +1684,18 @@ void QWindow::setY(int arg)
\property QWindow::width
\brief the width of the window's geometry
*/
-void QWindow::setWidth(int arg)
+void QWindow::setWidth(int w)
{
- if (width() != arg)
- resize(arg, height());
+ resize(w, height());
}
/*!
\property QWindow::height
\brief the height of the window's geometry
*/
-void QWindow::setHeight(int arg)
+void QWindow::setHeight(int h)
{
- if (height() != arg)
- resize(width(), arg);
+ resize(width(), h);
}
/*!
@@ -1959,12 +2017,16 @@ void QWindow::resize(int w, int h)
void QWindow::resize(const QSize &newSize)
{
Q_D(QWindow);
+
+ const QSize oldSize = size();
+ if (newSize == oldSize)
+ return;
+
d->positionPolicy = QWindowPrivate::WindowFrameExclusive;
if (d->platformWindow) {
d->platformWindow->setGeometry(
QHighDpi::toNativeWindowGeometry(QRect(position(), newSize), this));
} else {
- const QSize oldSize = d->geometry.size();
d->geometry.setSize(newSize);
if (newSize.width() != oldSize.width())
emit widthChanged(newSize.width());
@@ -2001,6 +2063,16 @@ void QWindowPrivate::destroy()
QObject *object = childrenWindows.at(i);
if (object->isWindowType()) {
QWindow *w = static_cast<QWindow*>(object);
+ auto *childPlatformWindow = w->handle();
+ if (!childPlatformWindow)
+ continue;
+
+ // Decouple the foreign window from this window,
+ // so that destroying our native handle doesn't
+ // bring down the foreign window as well.
+ if (childPlatformWindow->isForeignWindow())
+ childPlatformWindow->setParent(nullptr);
+
qt_window_private(w)->destroy();
}
}
@@ -2173,20 +2245,26 @@ QObject *QWindow::focusObject() const
/*!
Shows the window.
- This is equivalent to calling showFullScreen(), showMaximized(), or showNormal(),
+ For child windows, this is equivalent to calling showNormal().
+ Otherwise, it is equivalent to calling showFullScreen(), showMaximized(), or showNormal(),
depending on the platform's default behavior for the window type and flags.
\sa showFullScreen(), showMaximized(), showNormal(), hide(), QStyleHints::showIsFullScreen(), flags()
*/
void QWindow::show()
{
- Qt::WindowState defaultState = QGuiApplicationPrivate::platformIntegration()->defaultWindowState(d_func()->windowFlags);
- if (defaultState == Qt::WindowFullScreen)
- showFullScreen();
- else if (defaultState == Qt::WindowMaximized)
- showMaximized();
- else
+ if (parent()) {
showNormal();
+ } else {
+ const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ Qt::WindowState defaultState = platformIntegration->defaultWindowState(d_func()->windowFlags);
+ if (defaultState == Qt::WindowFullScreen)
+ showFullScreen();
+ else if (defaultState == Qt::WindowMaximized)
+ showMaximized();
+ else
+ showNormal();
+ }
}
/*!
@@ -2576,16 +2654,14 @@ bool QWindow::event(QEvent *ev)
This logic could be simplified by always synthesizing events in
QGuiApplicationPrivate, or perhaps even in each QPA plugin. See QTBUG-93486.
*/
- static const QEvent::Type contextMenuTrigger =
- QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool() ?
- QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
auto asMouseEvent = [](QEvent *ev) {
const auto t = ev->type();
return t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease
? static_cast<QMouseEvent *>(ev) : nullptr ;
};
- if (QMouseEvent *me = asMouseEvent(ev); me &&
- ev->type() == contextMenuTrigger && me->button() == Qt::RightButton) {
+ if (QMouseEvent *me = asMouseEvent(ev);
+ me && ev->type() == QGuiApplicationPrivate::contextMenuEventType()
+ && me->button() == Qt::RightButton) {
QContextMenuEvent e(QContextMenuEvent::Mouse, me->position().toPoint(),
me->globalPosition().toPoint(), me->modifiers());
QGuiApplication::sendEvent(this, &e);
@@ -2792,7 +2868,12 @@ QPointF QWindow::mapToGlobal(const QPointF &pos) const
// Map the position (and the window's global position) to native coordinates, perform
// the addition, and then map back to device independent coordinates.
QPointF nativeLocalPos = QHighDpi::toNativeLocalPosition(pos, this);
- QPointF nativeWindowGlobalPos = QHighDpi::toNativeGlobalPosition(QPointF(d->globalPosition()), this);
+ // Get the native window position directly from the platform window
+ // if available (it can be null if the window hasn't been shown yet),
+ // or fall back to scaling the QWindow position.
+ QPointF nativeWindowGlobalPos = d->platformWindow
+ ? d->platformWindow->mapToGlobal(QPoint(0,0)).toPointF()
+ : QHighDpi::toNativeGlobalPosition(QPointF(d->globalPosition()), this);
QPointF nativeGlobalPos = nativeLocalPos + nativeWindowGlobalPos;
QPointF deviceIndependentGlobalPos = QHighDpi::fromNativeGlobalPosition(nativeGlobalPos, this);
return deviceIndependentGlobalPos;
@@ -2830,7 +2911,12 @@ QPointF QWindow::mapFromGlobal(const QPointF &pos) const
// Calculate local position in the native coordinate system. (See comment for the
// corresponding mapToGlobal() code above).
QPointF nativeGlobalPos = QHighDpi::toNativeGlobalPosition(pos, this);
- QPointF nativeWindowGlobalPos = QHighDpi::toNativeGlobalPosition(QPointF(d->globalPosition()), this);
+ // Get the native window position directly from the platform window
+ // if available (it can be null if the window hasn't been shown yet),
+ // or fall back to scaling the QWindow position.
+ QPointF nativeWindowGlobalPos = d->platformWindow
+ ? d->platformWindow->mapToGlobal(QPoint(0,0)).toPointF()
+ : QHighDpi::toNativeGlobalPosition(QPointF(d->globalPosition()), this);
QPointF nativeLocalPos = nativeGlobalPos - nativeWindowGlobalPos;
QPointF deviceIndependentLocalPos = QHighDpi::fromNativeLocalPosition(nativeLocalPos, this);
return deviceIndependentLocalPos;
@@ -2912,7 +2998,11 @@ QWindow *QWindow::fromWinId(WId id)
}
QWindow *window = new QWindow;
- qt_window_private(window)->create(false, id);
+
+ // Persist the winId in a private property so that we
+ // can recreate the window after being destroyed.
+ window->setProperty(kForeignWindowId, id);
+ window->create();
if (!window->handle()) {
delete window;
@@ -3061,7 +3151,7 @@ void *QWindow::resolveInterface(const char *name, int revision) const
QT_NATIVE_INTERFACE_RETURN_IF(QCocoaWindow, platformWindow);
#endif
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
QT_NATIVE_INTERFACE_RETURN_IF(QWaylandWindow, platformWindow);
#endif
diff --git a/src/gui/kernel/qwindow_p.h b/src/gui/kernel/qwindow_p.h
index 96beb17bec..40ab06af8b 100644
--- a/src/gui/kernel/qwindow_p.h
+++ b/src/gui/kernel/qwindow_p.h
@@ -26,6 +26,8 @@
#include <QtGui/qicon.h>
#include <QtGui/qpalette.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QWindowPrivate : public QObjectPrivate
@@ -42,7 +44,7 @@ public:
QWindowPrivate();
~QWindowPrivate() override;
- void init(QScreen *targetScreen = nullptr);
+ void init(QWindow *parent, QScreen *targetScreen = nullptr);
#ifndef QT_NO_CURSOR
void setCursor(const QCursor *c = nullptr);
@@ -64,7 +66,7 @@ public:
void updateSiblingPosition(SiblingPosition);
bool windowRecreationRequired(QScreen *newScreen) const;
- void create(bool recursive, WId nativeHandle = 0);
+ void create(bool recursive);
void destroy();
void setTopLevelScreen(QScreen *newScreen, bool recreate);
void connectToScreen(QScreen *topLevelScreen);
@@ -74,6 +76,16 @@ public:
void setTransientParent(QWindow *parent);
virtual void clearFocusObject();
+
+ enum class FocusTarget {
+ First,
+ Last,
+ Current,
+ Next,
+ Prev
+ };
+ virtual void setFocusToTarget(FocusTarget, Qt::FocusReason) {}
+
virtual QRectF closestAcceptableGeometry(const QRectF &rect) const;
void setMinOrMaxSize(QSize *oldSizeMember, const QSize &size,
@@ -89,7 +101,7 @@ public:
void setAutomaticPositionAndResizeEnabled(bool a)
{ positionAutomatic = resizeAutomatic = a; }
- void updateDevicePixelRatio();
+ bool updateDevicePixelRatio();
static QWindowPrivate *get(QWindow *window) { return window->d_func(); }
@@ -143,7 +155,6 @@ public:
bool hasCursor = false;
#endif
- bool compositing = false;
QElapsedTimer lastComposeTime;
#if QT_CONFIG(vulkan)
diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp
index be2157c8b9..1875594300 100644
--- a/src/gui/kernel/qwindowsysteminterface.cpp
+++ b/src/gui/kernel/qwindowsysteminterface.cpp
@@ -240,9 +240,9 @@ void QWindowSystemInterface::handleEnterLeaveEvent(QWindow *enter, QWindow *leav
handleEnterEvent(enter, local, global);
}
-QT_DEFINE_QPA_EVENT_HANDLER(void, handleWindowActivated, QWindow *window, Qt::FocusReason r)
+QT_DEFINE_QPA_EVENT_HANDLER(void, handleFocusWindowChanged, QWindow *window, Qt::FocusReason r)
{
- handleWindowSystemEvent<QWindowSystemInterfacePrivate::ActivatedWindowEvent, Delivery>(window, r);
+ handleWindowSystemEvent<QWindowSystemInterfacePrivate::FocusWindowEvent, Delivery>(window, r);
}
QT_DEFINE_QPA_EVENT_HANDLER(void, handleWindowStateChanged, QWindow *window, Qt::WindowStates newState, int oldState)
@@ -814,6 +814,11 @@ void QWindowSystemInterface::handleScreenGeometryChange(QScreen *screen, const Q
void QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(QScreen *screen, qreal dpiX, qreal dpiY)
{
+ // Keep QHighDpiScaling::m_active in sync with platform screen state, in
+ // order to make scaling calls made during DPI change use the new state.
+ // FIXME: Remove when QHighDpiScaling::m_active has been removed.
+ QHighDpiScaling::updateHighDpiScaling();
+
const QDpi effectiveDpi = QPlatformScreen::overrideDpi(QDpi{dpiX, dpiY});
handleWindowSystemEvent<QWindowSystemInterfacePrivate::ScreenLogicalDotsPerInchEvent>(screen,
effectiveDpi.first, effectiveDpi.second);
@@ -1205,6 +1210,13 @@ Q_GUI_EXPORT bool qt_sendShortcutOverrideEvent(QObject *o, ulong timestamp, int
#endif
}
+Q_GUI_EXPORT void qt_handleWheelEvent(QWindow *window, const QPointF &local, const QPointF &global,
+ QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods,
+ Qt::ScrollPhase phase)
+{
+ QWindowSystemInterface::handleWheelEvent(window, local, global, pixelDelta, angleDelta, mods, phase);
+}
+
namespace QTest
{
Q_GUI_EXPORT QPointingDevice * createTouchDevice(QInputDevice::DeviceType devType,
diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h
index 7b33bf3183..4fc61a475d 100644
--- a/src/gui/kernel/qwindowsysteminterface.h
+++ b/src/gui/kernel/qwindowsysteminterface.h
@@ -157,8 +157,9 @@ public:
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
static void handleLeaveEvent(QWindow *window);
static void handleEnterLeaveEvent(QWindow *enter, QWindow *leave, const QPointF &local = QPointF(), const QPointF& global = QPointF());
+
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
- static void handleWindowActivated(QWindow *window, Qt::FocusReason r = Qt::OtherFocusReason);
+ static void handleFocusWindowChanged(QWindow *window, Qt::FocusReason r = Qt::OtherFocusReason);
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
static void handleWindowStateChanged(QWindow *window, Qt::WindowStates newState, int oldState = -1);
@@ -230,6 +231,8 @@ public:
Qt::MouseButtons buttons = {}, int xTilt = 0, int yTilt = 0,
qreal tangentialPressure = 0, qreal rotation = 0, int z = 0,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
+
+ // The following 4 functions are deprecated (QTBUG-114560)
static bool handleTabletEnterProximityEvent(ulong timestamp, int deviceType, int pointerType, qint64 uid);
static void handleTabletEnterProximityEvent(int deviceType, int pointerType, qint64 uid);
static bool handleTabletLeaveProximityEvent(ulong timestamp, int deviceType, int pointerType, qint64 uid);
diff --git a/src/gui/kernel/qwindowsysteminterface_p.h b/src/gui/kernel/qwindowsysteminterface_p.h
index b08abc0a95..51ab58fc99 100644
--- a/src/gui/kernel/qwindowsysteminterface_p.h
+++ b/src/gui/kernel/qwindowsysteminterface_p.h
@@ -40,7 +40,7 @@ public:
GeometryChange = 0x02,
Enter = UserInputEvent | 0x03,
Leave = UserInputEvent | 0x04,
- ActivatedWindow = 0x05,
+ FocusWindow = 0x05,
WindowStateChanged = 0x06,
Mouse = UserInputEvent | 0x07,
Wheel = UserInputEvent | 0x09,
@@ -125,12 +125,12 @@ public:
QPointer<QWindow> leave;
};
- class ActivatedWindowEvent : public WindowSystemEvent {
+ class FocusWindowEvent : public WindowSystemEvent {
public:
- explicit ActivatedWindowEvent(QWindow *activatedWindow, Qt::FocusReason r)
- : WindowSystemEvent(ActivatedWindow), activated(activatedWindow), reason(r)
+ explicit FocusWindowEvent(QWindow *focusedWindow, Qt::FocusReason r)
+ : WindowSystemEvent(FocusWindow), focused(focusedWindow), reason(r)
{ }
- QPointer<QWindow> activated;
+ QPointer<QWindow> focused;
Qt::FocusReason reason;
};
diff --git a/src/gui/math3d/qmatrix4x4.cpp b/src/gui/math3d/qmatrix4x4.cpp
index 39625cdfb4..d04a502519 100644
--- a/src/gui/math3d/qmatrix4x4.cpp
+++ b/src/gui/math3d/qmatrix4x4.cpp
@@ -100,7 +100,7 @@ QMatrix4x4::QMatrix4x4(const float *values)
*/
/*!
- \fn QGenericMatrix<N, M, float> QMatrix4x4::toGenericMatrix() const
+ \fn template <int N, int M> QGenericMatrix<N, M, float> QMatrix4x4::toGenericMatrix() const
Constructs a NxM generic matrix from the left-most N columns and
top-most M rows of this 4x4 matrix. If N or M is greater than 4,
diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp
index 018f6fb731..e7c5945208 100644
--- a/src/gui/math3d/qquaternion.cpp
+++ b/src/gui/math3d/qquaternion.cpp
@@ -26,14 +26,14 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn QQuaternion::QQuaternion()
+ \fn QQuaternion::QQuaternion() noexcept
Constructs an identity quaternion (1, 0, 0, 0), i.e. with the vector (0, 0, 0)
and scalar 1.
*/
/*!
- \fn QQuaternion::QQuaternion(Qt::Initialization)
+ \fn QQuaternion::QQuaternion(Qt::Initialization) noexcept
\since 5.5
\internal
@@ -41,7 +41,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn QQuaternion::QQuaternion(float scalar, float xpos, float ypos, float zpos)
+ \fn QQuaternion::QQuaternion(float scalar, float xpos, float ypos, float zpos) noexcept
Constructs a quaternion with the vector (\a xpos, \a ypos, \a zpos)
and \a scalar.
@@ -50,7 +50,7 @@ QT_BEGIN_NAMESPACE
#ifndef QT_NO_VECTOR3D
/*!
- \fn QQuaternion::QQuaternion(float scalar, const QVector3D& vector)
+ \fn QQuaternion::QQuaternion(float scalar, const QVector3D &vector) noexcept
Constructs a quaternion vector from the specified \a vector and
\a scalar.
@@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn QVector3D QQuaternion::vector() const
+ \fn QVector3D QQuaternion::vector() const noexcept
Returns the vector component of this quaternion.
@@ -67,7 +67,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn void QQuaternion::setVector(const QVector3D& vector)
+ \fn void QQuaternion::setVector(const QVector3D &vector) noexcept
Sets the vector component of this quaternion to \a vector.
@@ -77,7 +77,7 @@ QT_BEGIN_NAMESPACE
#endif
/*!
- \fn void QQuaternion::setVector(float x, float y, float z)
+ \fn void QQuaternion::setVector(float x, float y, float z) noexcept
Sets the vector component of this quaternion to (\a x, \a y, \a z).
@@ -87,13 +87,13 @@ QT_BEGIN_NAMESPACE
#ifndef QT_NO_VECTOR4D
/*!
- \fn QQuaternion::QQuaternion(const QVector4D& vector)
+ \fn QQuaternion::QQuaternion(const QVector4D &vector) noexcept
Constructs a quaternion from the components of \a vector.
*/
/*!
- \fn QVector4D QQuaternion::toVector4D() const
+ \fn QVector4D QQuaternion::toVector4D() const noexcept
Returns this quaternion as a 4D vector.
*/
@@ -101,14 +101,14 @@ QT_BEGIN_NAMESPACE
#endif
/*!
- \fn bool QQuaternion::isNull() const
+ \fn bool QQuaternion::isNull() const noexcept
Returns \c true if the x, y, z, and scalar components of this
quaternion are set to 0.0; otherwise returns \c false.
*/
/*!
- \fn bool QQuaternion::isIdentity() const
+ \fn bool QQuaternion::isIdentity() const noexcept
Returns \c true if the x, y, and z components of this
quaternion are set to 0.0, and the scalar component is set
@@ -116,7 +116,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn float QQuaternion::x() const
+ \fn float QQuaternion::x() const noexcept
Returns the x coordinate of this quaternion's vector.
@@ -124,7 +124,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn float QQuaternion::y() const
+ \fn float QQuaternion::y() const noexcept
Returns the y coordinate of this quaternion's vector.
@@ -132,7 +132,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn float QQuaternion::z() const
+ \fn float QQuaternion::z() const noexcept
Returns the z coordinate of this quaternion's vector.
@@ -140,7 +140,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn float QQuaternion::scalar() const
+ \fn float QQuaternion::scalar() const noexcept
Returns the scalar component of this quaternion.
@@ -148,7 +148,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn void QQuaternion::setX(float x)
+ \fn void QQuaternion::setX(float x) noexcept
Sets the x coordinate of this quaternion's vector to the given
\a x coordinate.
@@ -157,7 +157,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn void QQuaternion::setY(float y)
+ \fn void QQuaternion::setY(float y) noexcept
Sets the y coordinate of this quaternion's vector to the given
\a y coordinate.
@@ -166,7 +166,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn void QQuaternion::setZ(float z)
+ \fn void QQuaternion::setZ(float z) noexcept
Sets the z coordinate of this quaternion's vector to the given
\a z coordinate.
@@ -175,7 +175,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn void QQuaternion::setScalar(float scalar)
+ \fn void QQuaternion::setScalar(float scalar) noexcept
Sets the scalar component of this quaternion to \a scalar.
@@ -183,7 +183,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \fn float QQuaternion::dotProduct(const QQuaternion &q1, const QQuaternion &q2)
+ \fn float QQuaternion::dotProduct(const QQuaternion &q1, const QQuaternion &q2) noexcept
\since 5.5
Returns the dot product of \a q1 and \a q2.
@@ -227,8 +227,6 @@ float QQuaternion::lengthSquared() const
QQuaternion QQuaternion::normalized() const
{
const float scale = length();
- if (qFuzzyCompare(scale, 1.0f))
- return *this;
if (qFuzzyIsNull(scale))
return QQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
return *this / scale;
@@ -243,7 +241,7 @@ QQuaternion QQuaternion::normalized() const
void QQuaternion::normalize()
{
const float len = length();
- if (qFuzzyCompare(len, 1.0f) || qFuzzyIsNull(len))
+ if (qFuzzyIsNull(len))
return;
xp /= len;
@@ -253,7 +251,7 @@ void QQuaternion::normalize()
}
/*!
- \fn QQuaternion QQuaternion::inverted() const
+ \fn QQuaternion QQuaternion::inverted() const noexcept
\since 5.5
Returns the inverse of this quaternion.
@@ -263,7 +261,7 @@ void QQuaternion::normalize()
*/
/*!
- \fn QQuaternion QQuaternion::conjugated() const
+ \fn QQuaternion QQuaternion::conjugated() const noexcept
\since 5.5
Returns the conjugate of this quaternion, which is
@@ -280,13 +278,13 @@ void QQuaternion::normalize()
\snippet code/src_gui_math3d_qquaternion.cpp 1
*/
-QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
+QVector3D QQuaternion::rotatedVector(const QVector3D &vector) const
{
return (*this * QQuaternion(0, vector) * conjugated()).vector();
}
/*!
- \fn QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion)
+ \fn QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion) noexcept
Adds the given \a quaternion to this quaternion and returns a reference to
this quaternion.
@@ -295,7 +293,7 @@ QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
*/
/*!
- \fn QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion)
+ \fn QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion) noexcept
Subtracts the given \a quaternion from this quaternion and returns a
reference to this quaternion.
@@ -304,7 +302,7 @@ QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
*/
/*!
- \fn QQuaternion &QQuaternion::operator*=(float factor)
+ \fn QQuaternion &QQuaternion::operator*=(float factor) noexcept
Multiplies this quaternion's components by the given \a factor, and
returns a reference to this quaternion.
@@ -313,7 +311,7 @@ QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
*/
/*!
- \fn QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion)
+ \fn QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion) noexcept
Multiplies this quaternion by \a quaternion and returns a reference
to this quaternion.
@@ -331,7 +329,7 @@ QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
#ifndef QT_NO_VECTOR3D
/*!
- \fn void QQuaternion::getAxisAndAngle(QVector3D *axis, float *angle) const
+ \fn void QQuaternion::getAxisAndAngle(QVector3D *axis, float *angle) const noexcept
\since 5.5
\overload
@@ -347,7 +345,7 @@ QVector3D QQuaternion::rotatedVector(const QVector3D& vector) const
\sa getAxisAndAngle()
*/
-QQuaternion QQuaternion::fromAxisAndAngle(const QVector3D& axis, float angle)
+QQuaternion QQuaternion::fromAxisAndAngle(const QVector3D &axis, float angle)
{
// Algorithm from:
// http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q56
@@ -388,7 +386,7 @@ void QQuaternion::getAxisAndAngle(float *x, float *y, float *z, float *angle) co
*y = yp / length;
*z = zp / length;
}
- *angle = qRadiansToDegrees(2.0f * std::acos(wp));
+ *angle = qRadiansToDegrees(2.0f * std::atan2(length, wp));
} else {
// angle is 0 (mod 2*pi), so any axis will fit
*x = *y = *z = *angle = 0.0f;
@@ -740,21 +738,21 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
#endif // QT_NO_VECTOR3D
/*!
- \fn bool QQuaternion::operator==(const QQuaternion &q1, const QQuaternion &q2)
+ \fn bool QQuaternion::operator==(const QQuaternion &q1, const QQuaternion &q2) noexcept
Returns \c true if \a q1 is equal to \a q2; otherwise returns \c false.
This operator uses an exact floating-point comparison.
*/
/*!
- \fn bool QQuaternion::operator!=(const QQuaternion &q1, const QQuaternion &q2)
+ \fn bool QQuaternion::operator!=(const QQuaternion &q1, const QQuaternion &q2) noexcept
Returns \c true if \a q1 is not equal to \a q2; otherwise returns \c false.
This operator uses an exact floating-point comparison.
*/
/*!
- \fn const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2)
+ \fn const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2) noexcept
\relates QQuaternion
Returns a QQuaternion object that is the sum of the given quaternions,
@@ -764,7 +762,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
*/
/*!
- \fn const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2)
+ \fn const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2) noexcept
\relates QQuaternion
Returns a QQuaternion object that is formed by subtracting
@@ -774,7 +772,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
*/
/*!
- \fn const QQuaternion operator*(float factor, const QQuaternion &quaternion)
+ \fn const QQuaternion operator*(float factor, const QQuaternion &quaternion) noexcept
\relates QQuaternion
Returns a copy of the given \a quaternion, multiplied by the
@@ -784,7 +782,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
*/
/*!
- \fn const QQuaternion operator*(const QQuaternion &quaternion, float factor)
+ \fn const QQuaternion operator*(const QQuaternion &quaternion, float factor) noexcept
\relates QQuaternion
Returns a copy of the given \a quaternion, multiplied by the
@@ -794,7 +792,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
*/
/*!
- \fn const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2)
+ \fn const QQuaternion operator*(const QQuaternion &q1, const QQuaternion &q2) noexcept
\relates QQuaternion
Multiplies \a q1 and \a q2 using quaternion multiplication.
@@ -805,7 +803,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
*/
/*!
- \fn const QQuaternion operator-(const QQuaternion &quaternion)
+ \fn const QQuaternion operator-(const QQuaternion &quaternion) noexcept
\relates QQuaternion
\overload
@@ -828,7 +826,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
#ifndef QT_NO_VECTOR3D
/*!
- \fn QVector3D operator*(const QQuaternion &quaternion, const QVector3D &vec)
+ \fn QVector3D operator*(const QQuaternion &quaternion, const QVector3D &vec) noexcept
\since 5.5
\relates QQuaternion
@@ -838,7 +836,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
#endif
/*!
- \fn bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2)
+ \fn bool qFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) noexcept
\relates QQuaternion
Returns \c true if \a q1 and \a q2 are equal, allowing for a small
@@ -857,7 +855,7 @@ QQuaternion QQuaternion::rotationTo(const QVector3D &from, const QVector3D &to)
\sa nlerp()
*/
QQuaternion QQuaternion::slerp
- (const QQuaternion& q1, const QQuaternion& q2, float t)
+ (const QQuaternion &q1, const QQuaternion &q2, float t)
{
// Handle the easy cases first.
if (t <= 0.0f)
@@ -906,7 +904,7 @@ QQuaternion QQuaternion::slerp
\sa slerp()
*/
QQuaternion QQuaternion::nlerp
- (const QQuaternion& q1, const QQuaternion& q2, float t)
+ (const QQuaternion &q1, const QQuaternion &q2, float t)
{
// Handle the easy cases first.
if (t <= 0.0f)
diff --git a/src/gui/math3d/qquaternion.h b/src/gui/math3d/qquaternion.h
index 226a077d4c..7cfbd9b818 100644
--- a/src/gui/math3d/qquaternion.h
+++ b/src/gui/math3d/qquaternion.h
@@ -17,117 +17,123 @@ QT_BEGIN_NAMESPACE
class QMatrix4x4;
class QVariant;
-class Q_GUI_EXPORT QQuaternion
+class QT6_ONLY(Q_GUI_EXPORT) QQuaternion
{
public:
- QQuaternion();
- explicit QQuaternion(Qt::Initialization) {}
- QQuaternion(float scalar, float xpos, float ypos, float zpos);
+ constexpr QQuaternion() noexcept;
+ explicit QQuaternion(Qt::Initialization) noexcept {}
+ constexpr QQuaternion(float scalar, float xpos, float ypos, float zpos) noexcept;
#ifndef QT_NO_VECTOR3D
- QQuaternion(float scalar, const QVector3D& vector);
+ constexpr QQuaternion(float scalar, const QVector3D &vector) noexcept;
#endif
#ifndef QT_NO_VECTOR4D
- explicit QQuaternion(const QVector4D& vector);
+ constexpr explicit QQuaternion(const QVector4D &vector) noexcept;
#endif
- bool isNull() const;
- bool isIdentity() const;
+ constexpr bool isNull() const noexcept;
+ constexpr bool isIdentity() const noexcept;
#ifndef QT_NO_VECTOR3D
- QVector3D vector() const;
- void setVector(const QVector3D& vector);
+ constexpr QVector3D vector() const noexcept;
+ constexpr void setVector(const QVector3D &vector) noexcept;
#endif
- void setVector(float x, float y, float z);
+ constexpr void setVector(float x, float y, float z) noexcept;
- float x() const;
- float y() const;
- float z() const;
- float scalar() const;
+ constexpr float x() const noexcept;
+ constexpr float y() const noexcept;
+ constexpr float z() const noexcept;
+ constexpr float scalar() const noexcept;
- void setX(float x);
- void setY(float y);
- void setZ(float z);
- void setScalar(float scalar);
+ constexpr void setX(float x) noexcept;
+ constexpr void setY(float y) noexcept;
+ constexpr void setZ(float z) noexcept;
+ constexpr void setScalar(float scalar) noexcept;
- constexpr static inline float dotProduct(const QQuaternion &q1, const QQuaternion &q2);
+ constexpr static float dotProduct(const QQuaternion &q1, const QQuaternion &q2) noexcept;
- float length() const;
- float lengthSquared() const;
+ // ### Qt 7: make the next four constexpr
+ // (perhaps using std::hypot, constexpr in C++26, or constexpr qHypot)
+ QT7_ONLY(Q_GUI_EXPORT) float length() const;
+ QT7_ONLY(Q_GUI_EXPORT) float lengthSquared() const;
- [[nodiscard]] QQuaternion normalized() const;
- void normalize();
+ [[nodiscard]] QT7_ONLY(Q_GUI_EXPORT) QQuaternion normalized() const;
+ QT7_ONLY(Q_GUI_EXPORT) void normalize();
- inline QQuaternion inverted() const;
+ constexpr QQuaternion inverted() const noexcept;
- [[nodiscard]] QQuaternion conjugated() const;
+ [[nodiscard]] constexpr QQuaternion conjugated() const noexcept;
- QVector3D rotatedVector(const QVector3D& vector) const;
+ QT7_ONLY(Q_GUI_EXPORT) QVector3D rotatedVector(const QVector3D &vector) const;
- QQuaternion &operator+=(const QQuaternion &quaternion);
- QQuaternion &operator-=(const QQuaternion &quaternion);
- QQuaternion &operator*=(float factor);
- QQuaternion &operator*=(const QQuaternion &quaternion);
- QQuaternion &operator/=(float divisor);
+ constexpr QQuaternion &operator+=(const QQuaternion &quaternion) noexcept;
+ constexpr QQuaternion &operator-=(const QQuaternion &quaternion) noexcept;
+ constexpr QQuaternion &operator*=(float factor) noexcept;
+ constexpr QQuaternion &operator*=(const QQuaternion &quaternion) noexcept;
+ constexpr QQuaternion &operator/=(float divisor);
QT_WARNING_PUSH
QT_WARNING_DISABLE_FLOAT_COMPARE
- friend inline bool operator==(const QQuaternion &q1, const QQuaternion &q2) noexcept
+ friend constexpr bool operator==(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return q1.wp == q2.wp && q1.xp == q2.xp && q1.yp == q2.yp && q1.zp == q2.zp;
}
- friend inline bool operator!=(const QQuaternion &q1, const QQuaternion &q2) noexcept
+ friend constexpr bool operator!=(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return !(q1 == q2);
}
QT_WARNING_POP
- friend inline const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2);
- friend inline const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2);
- friend inline const QQuaternion operator*(float factor, const QQuaternion &quaternion);
- friend inline const QQuaternion operator*(const QQuaternion &quaternion, float factor);
- friend inline const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2);
- friend inline const QQuaternion operator-(const QQuaternion &quaternion);
- friend inline const QQuaternion operator/(const QQuaternion &quaternion, float divisor);
+ friend constexpr QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2) noexcept;
+ friend constexpr QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2) noexcept;
+ friend constexpr QQuaternion operator*(float factor, const QQuaternion &quaternion) noexcept;
+ friend constexpr QQuaternion operator*(const QQuaternion &quaternion, float factor) noexcept;
+ friend constexpr QQuaternion operator*(const QQuaternion &q1, const QQuaternion &q2) noexcept;
+ friend constexpr QQuaternion operator-(const QQuaternion &quaternion) noexcept;
+ friend constexpr QQuaternion operator/(const QQuaternion &quaternion, float divisor);
- friend inline bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2);
+ friend constexpr bool qFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) noexcept;
#ifndef QT_NO_VECTOR4D
- QVector4D toVector4D() const;
+ constexpr QVector4D toVector4D() const noexcept;
#endif
- operator QVariant() const;
+ QT7_ONLY(Q_GUI_EXPORT) operator QVariant() const;
#ifndef QT_NO_VECTOR3D
inline void getAxisAndAngle(QVector3D *axis, float *angle) const;
- static QQuaternion fromAxisAndAngle(const QVector3D& axis, float angle);
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromAxisAndAngle(const QVector3D &axis, float angle);
#endif
- void getAxisAndAngle(float *x, float *y, float *z, float *angle) const;
- static QQuaternion fromAxisAndAngle
- (float x, float y, float z, float angle);
+ QT7_ONLY(Q_GUI_EXPORT) void getAxisAndAngle(float *x, float *y, float *z, float *angle) const;
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromAxisAndAngle(float x, float y, float z,
+ float angle);
#ifndef QT_NO_VECTOR3D
inline QVector3D toEulerAngles() const;
- static inline QQuaternion fromEulerAngles(const QVector3D &eulerAngles);
+ QT7_ONLY(Q_GUI_EXPORT) static inline QQuaternion fromEulerAngles(const QVector3D &eulerAngles);
#endif
- void getEulerAngles(float *pitch, float *yaw, float *roll) const;
- static QQuaternion fromEulerAngles(float pitch, float yaw, float roll);
+ QT7_ONLY(Q_GUI_EXPORT) void getEulerAngles(float *pitch, float *yaw, float *roll) const;
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromEulerAngles(float pitch, float yaw, float roll);
- QMatrix3x3 toRotationMatrix() const;
- static QQuaternion fromRotationMatrix(const QMatrix3x3 &rot3x3);
+ QT7_ONLY(Q_GUI_EXPORT) QMatrix3x3 toRotationMatrix() const;
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromRotationMatrix(const QMatrix3x3 &rot3x3);
#ifndef QT_NO_VECTOR3D
- void getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const;
- static QQuaternion fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis);
+ QT7_ONLY(Q_GUI_EXPORT) void getAxes(QVector3D *xAxis, QVector3D *yAxis, QVector3D *zAxis) const;
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromAxes(const QVector3D &xAxis,
+ const QVector3D &yAxis,
+ const QVector3D &zAxis);
- static QQuaternion fromDirection(const QVector3D &direction, const QVector3D &up);
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion fromDirection(const QVector3D &direction,
+ const QVector3D &up);
- static QQuaternion rotationTo(const QVector3D &from, const QVector3D &to);
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion rotationTo(const QVector3D &from,
+ const QVector3D &to);
#endif
- static QQuaternion slerp
- (const QQuaternion& q1, const QQuaternion& q2, float t);
- static QQuaternion nlerp
- (const QQuaternion& q1, const QQuaternion& q2, float t);
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion slerp(const QQuaternion &q1, const QQuaternion &q2,
+ float t);
+ QT7_ONLY(Q_GUI_EXPORT) static QQuaternion nlerp(const QQuaternion &q1, const QQuaternion &q2,
+ float t);
private:
float wp, xp, yp, zp;
@@ -135,40 +141,41 @@ private:
Q_DECLARE_TYPEINFO(QQuaternion, Q_PRIMITIVE_TYPE);
-inline QQuaternion::QQuaternion() : wp(1.0f), xp(0.0f), yp(0.0f), zp(0.0f) {}
+constexpr QQuaternion::QQuaternion() noexcept : wp(1.0f), xp(0.0f), yp(0.0f), zp(0.0f) {}
-inline QQuaternion::QQuaternion(float aScalar, float xpos, float ypos, float zpos) : wp(aScalar), xp(xpos), yp(ypos), zp(zpos) {}
+constexpr QQuaternion::QQuaternion(float aScalar, float xpos, float ypos, float zpos) noexcept
+ : wp(aScalar), xp(xpos), yp(ypos), zp(zpos) {}
QT_WARNING_PUSH
QT_WARNING_DISABLE_FLOAT_COMPARE
-inline bool QQuaternion::isNull() const
+constexpr bool QQuaternion::isNull() const noexcept
{
return wp == 0.0f && xp == 0.0f && yp == 0.0f && zp == 0.0f;
}
-inline bool QQuaternion::isIdentity() const
+constexpr bool QQuaternion::isIdentity() const noexcept
{
return wp == 1.0f && xp == 0.0f && yp == 0.0f && zp == 0.0f;
}
QT_WARNING_POP
-inline float QQuaternion::x() const { return xp; }
-inline float QQuaternion::y() const { return yp; }
-inline float QQuaternion::z() const { return zp; }
-inline float QQuaternion::scalar() const { return wp; }
+constexpr float QQuaternion::x() const noexcept { return xp; }
+constexpr float QQuaternion::y() const noexcept { return yp; }
+constexpr float QQuaternion::z() const noexcept { return zp; }
+constexpr float QQuaternion::scalar() const noexcept { return wp; }
-inline void QQuaternion::setX(float aX) { xp = aX; }
-inline void QQuaternion::setY(float aY) { yp = aY; }
-inline void QQuaternion::setZ(float aZ) { zp = aZ; }
-inline void QQuaternion::setScalar(float aScalar) { wp = aScalar; }
+constexpr void QQuaternion::setX(float aX) noexcept { xp = aX; }
+constexpr void QQuaternion::setY(float aY) noexcept { yp = aY; }
+constexpr void QQuaternion::setZ(float aZ) noexcept { zp = aZ; }
+constexpr void QQuaternion::setScalar(float aScalar) noexcept { wp = aScalar; }
-constexpr inline float QQuaternion::dotProduct(const QQuaternion &q1, const QQuaternion &q2)
+constexpr float QQuaternion::dotProduct(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return q1.wp * q2.wp + q1.xp * q2.xp + q1.yp * q2.yp + q1.zp * q2.zp;
}
-inline QQuaternion QQuaternion::inverted() const
+constexpr QQuaternion QQuaternion::inverted() const noexcept
{
// Need some extra precision if the length is very small.
double len = double(wp) * double(wp) +
@@ -181,12 +188,12 @@ inline QQuaternion QQuaternion::inverted() const
return QQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
}
-inline QQuaternion QQuaternion::conjugated() const
+constexpr QQuaternion QQuaternion::conjugated() const noexcept
{
return QQuaternion(wp, -xp, -yp, -zp);
}
-inline QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion)
+constexpr QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion) noexcept
{
wp += quaternion.wp;
xp += quaternion.xp;
@@ -195,7 +202,7 @@ inline QQuaternion &QQuaternion::operator+=(const QQuaternion &quaternion)
return *this;
}
-inline QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion)
+constexpr QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion) noexcept
{
wp -= quaternion.wp;
xp -= quaternion.xp;
@@ -204,7 +211,7 @@ inline QQuaternion &QQuaternion::operator-=(const QQuaternion &quaternion)
return *this;
}
-inline QQuaternion &QQuaternion::operator*=(float factor)
+constexpr QQuaternion &QQuaternion::operator*=(float factor) noexcept
{
wp *= factor;
xp *= factor;
@@ -213,7 +220,7 @@ inline QQuaternion &QQuaternion::operator*=(float factor)
return *this;
}
-inline const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2)
+constexpr QQuaternion operator*(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
float yy = (q1.wp - q1.yp) * (q2.wp + q2.zp);
float zz = (q1.wp + q1.yp) * (q2.wp - q2.zp);
@@ -229,13 +236,13 @@ inline const QQuaternion operator*(const QQuaternion &q1, const QQuaternion& q2)
return QQuaternion(w, x, y, z);
}
-inline QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion)
+constexpr QQuaternion &QQuaternion::operator*=(const QQuaternion &quaternion) noexcept
{
*this = *this * quaternion;
return *this;
}
-inline QQuaternion &QQuaternion::operator/=(float divisor)
+constexpr QQuaternion &QQuaternion::operator/=(float divisor)
{
wp /= divisor;
xp /= divisor;
@@ -244,37 +251,37 @@ inline QQuaternion &QQuaternion::operator/=(float divisor)
return *this;
}
-inline const QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2)
+constexpr QQuaternion operator+(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return QQuaternion(q1.wp + q2.wp, q1.xp + q2.xp, q1.yp + q2.yp, q1.zp + q2.zp);
}
-inline const QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2)
+constexpr QQuaternion operator-(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return QQuaternion(q1.wp - q2.wp, q1.xp - q2.xp, q1.yp - q2.yp, q1.zp - q2.zp);
}
-inline const QQuaternion operator*(float factor, const QQuaternion &quaternion)
+constexpr QQuaternion operator*(float factor, const QQuaternion &quaternion) noexcept
{
return QQuaternion(quaternion.wp * factor, quaternion.xp * factor, quaternion.yp * factor, quaternion.zp * factor);
}
-inline const QQuaternion operator*(const QQuaternion &quaternion, float factor)
+constexpr QQuaternion operator*(const QQuaternion &quaternion, float factor) noexcept
{
return QQuaternion(quaternion.wp * factor, quaternion.xp * factor, quaternion.yp * factor, quaternion.zp * factor);
}
-inline const QQuaternion operator-(const QQuaternion &quaternion)
+constexpr QQuaternion operator-(const QQuaternion &quaternion) noexcept
{
return QQuaternion(-quaternion.wp, -quaternion.xp, -quaternion.yp, -quaternion.zp);
}
-inline const QQuaternion operator/(const QQuaternion &quaternion, float divisor)
+constexpr QQuaternion operator/(const QQuaternion &quaternion, float divisor)
{
return QQuaternion(quaternion.wp / divisor, quaternion.xp / divisor, quaternion.yp / divisor, quaternion.zp / divisor);
}
-inline bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2)
+constexpr bool qFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
return qFuzzyCompare(q1.wp, q2.wp) &&
qFuzzyCompare(q1.xp, q2.xp) &&
@@ -284,17 +291,17 @@ inline bool qFuzzyCompare(const QQuaternion& q1, const QQuaternion& q2)
#ifndef QT_NO_VECTOR3D
-inline QQuaternion::QQuaternion(float aScalar, const QVector3D& aVector)
+constexpr QQuaternion::QQuaternion(float aScalar, const QVector3D &aVector) noexcept
: wp(aScalar), xp(aVector.x()), yp(aVector.y()), zp(aVector.z()) {}
-inline void QQuaternion::setVector(const QVector3D& aVector)
+constexpr void QQuaternion::setVector(const QVector3D &aVector) noexcept
{
xp = aVector.x();
yp = aVector.y();
zp = aVector.z();
}
-inline QVector3D QQuaternion::vector() const
+constexpr QVector3D QQuaternion::vector() const noexcept
{
return QVector3D(xp, yp, zp);
}
@@ -325,7 +332,7 @@ inline QQuaternion QQuaternion::fromEulerAngles(const QVector3D &eulerAngles)
#endif
-inline void QQuaternion::setVector(float aX, float aY, float aZ)
+constexpr void QQuaternion::setVector(float aX, float aY, float aZ) noexcept
{
xp = aX;
yp = aY;
@@ -334,10 +341,10 @@ inline void QQuaternion::setVector(float aX, float aY, float aZ)
#ifndef QT_NO_VECTOR4D
-inline QQuaternion::QQuaternion(const QVector4D& aVector)
+constexpr QQuaternion::QQuaternion(const QVector4D &aVector) noexcept
: wp(aVector.w()), xp(aVector.x()), yp(aVector.y()), zp(aVector.z()) {}
-inline QVector4D QQuaternion::toVector4D() const
+constexpr QVector4D QQuaternion::toVector4D() const noexcept
{
return QVector4D(xp, yp, zp, wp);
}
diff --git a/src/gui/opengl/qopengl.h b/src/gui/opengl/qopengl.h
index 7e5b8e6961..e9a11080ce 100644
--- a/src/gui/opengl/qopengl.h
+++ b/src/gui/opengl/qopengl.h
@@ -8,9 +8,19 @@
#ifndef QT_NO_OPENGL
-// Windows always needs this to ensure that APIENTRY gets defined
+// On Windows we need to ensure that APIENTRY and WINGDIAPI are defined before
+// we can include gl.h. But we do not want to include <windows.h> in this public
+// Qt header, as it pollutes the global namespace with macros.
#if defined(Q_OS_WIN)
-# include <QtCore/qt_windows.h>
+# ifndef APIENTRY
+# define APIENTRY __stdcall
+# define Q_UNDEF_APIENTRY
+# endif // APIENTRY
+# ifndef WINGDIAPI
+# define WINGDIAPI __declspec(dllimport)
+# define Q_UNDEF_WINGDIAPI
+# endif // WINGDIAPI
+# define QT_APIENTRY __stdcall
#endif
// Note: Apple is a "controlled platform" for OpenGL ABI so we
@@ -128,11 +138,11 @@ typedef char GLchar;
// OS X 10.6 doesn't define these which are needed below
// OS X 10.7 and later define them in gl3.h
-#ifndef APIENTRY
-#define APIENTRY
+#ifndef QT_APIENTRY
+#define QT_APIENTRY
#endif
-#ifndef APIENTRYP
-#define APIENTRYP APIENTRY *
+#ifndef QT_APIENTRYP
+#define QT_APIENTRYP QT_APIENTRY *
#endif
#ifndef GLAPI
#define GLAPI extern
@@ -231,15 +241,15 @@ struct _cl_event;
#endif
#ifndef GL_ARB_debug_output
-typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const GLvoid *userParam);
+typedef void (QT_APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const GLvoid *userParam);
#endif
#ifndef GL_AMD_debug_output
-typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam);
+typedef void (QT_APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam);
#endif
#ifndef GL_KHR_debug
-typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const GLvoid *userParam);
+typedef void (QT_APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const GLvoid *userParam);
#endif
#ifndef GL_NV_vdpau_interop
@@ -256,8 +266,8 @@ typedef ptrdiff_t qopengl_GLintptr;
typedef ptrdiff_t qopengl_GLsizeiptr;
-#if defined(APIENTRY) && !defined(QOPENGLF_APIENTRY)
-# define QOPENGLF_APIENTRY APIENTRY
+#if defined(QT_APIENTRY) && !defined(QOPENGLF_APIENTRY)
+# define QOPENGLF_APIENTRY QT_APIENTRY
#endif
# ifndef QOPENGLF_APIENTRYP
@@ -271,6 +281,15 @@ typedef ptrdiff_t qopengl_GLsizeiptr;
QT_END_NAMESPACE
+#ifdef Q_UNDEF_WINGDIAPI
+# undef WINGDIAPI
+# undef Q_UNDEF_WINGDIAPI
+#endif
+#ifdef Q_UNDEF_APIENTRY
+# undef APIENTRY
+# undef Q_UNDEF_APIENTRY
+#endif
+
#endif // QT_NO_OPENGL
#endif // QOPENGL_H
diff --git a/src/gui/opengl/qopenglextensions_p.h b/src/gui/opengl/qopenglextensions_p.h
index fdb9b51f06..a6c4a68c6c 100644
--- a/src/gui/opengl/qopenglextensions_p.h
+++ b/src/gui/opengl/qopenglextensions_p.h
@@ -59,7 +59,9 @@ public:
StandardDerivatives = 0x02000000,
ASTCTextureCompression = 0x04000000,
ETC2TextureCompression = 0x08000000,
- HalfFloatVertex = 0x10000000
+ HalfFloatVertex = 0x10000000,
+ MultiView = 0x20000000,
+ MultiViewExtended = 0x40000000
};
Q_DECLARE_FLAGS(OpenGLExtensions, OpenGLExtension)
@@ -68,9 +70,9 @@ public:
GLvoid *glMapBuffer(GLenum target, GLenum access);
void glGetBufferSubData(GLenum target, qopengl_GLintptr offset, qopengl_GLsizeiptr size, GLvoid *data);
- void glDiscardFramebufferEXT (GLenum target, GLsizei numAttachments, const GLenum *attachments);
void flushShared();
+ void discardFramebuffer(GLenum target, GLsizei numAttachments, const GLenum *attachments);
QOpenGLExtensionsPrivate *d() const;
@@ -115,14 +117,6 @@ inline void QOpenGLExtensions::glGetBufferSubData(GLenum target, qopengl_GLintpt
Q_OPENGL_FUNCTIONS_DEBUG
}
-
-inline void QOpenGLExtensions::glDiscardFramebufferEXT (GLenum target, GLsizei numAttachments, const GLenum *attachments)
-{
- Q_D(QOpenGLExtensions);
- Q_ASSERT(QOpenGLExtensions::isInitialized(d));
- d->DiscardFramebuffer(target,numAttachments, attachments);
- Q_OPENGL_FUNCTIONS_DEBUG
-}
QT_END_NAMESPACE
#endif // QOPENGL_EXTENSIONS_P_H
diff --git a/src/gui/opengl/qopenglfunctions.cpp b/src/gui/opengl/qopenglfunctions.cpp
index ee76987566..c9352fbc31 100644
--- a/src/gui/opengl/qopenglfunctions.cpp
+++ b/src/gui/opengl/qopenglfunctions.cpp
@@ -348,6 +348,10 @@ static int qt_gl_resolve_extensions()
extensions |= QOpenGLExtensions::StandardDerivatives;
if (extensionMatcher.match("GL_ARB_half_float_vertex"))
extensions |= QOpenGLExtensions::HalfFloatVertex;
+ if (extensionMatcher.match("GL_OVR_multiview"))
+ extensions |= QOpenGLExtensions::MultiView;
+ if (extensionMatcher.match("GL_OVR_multiview2"))
+ extensions |= QOpenGLExtensions::MultiViewExtended;
if (ctx->isOpenGLES()) {
if (format.majorVersion() >= 2)
@@ -361,6 +365,7 @@ static int qt_gl_resolve_extensions()
| QOpenGLExtensions::FramebufferBlit
| QOpenGLExtensions::FramebufferMultisample
| QOpenGLExtensions::Sized8Formats
+ | QOpenGLExtensions::DiscardFramebuffer
| QOpenGLExtensions::StandardDerivatives
| QOpenGLExtensions::ETC2TextureCompression
| QOpenGLExtensions::HalfFloatVertex;
@@ -446,6 +451,9 @@ static int qt_gl_resolve_extensions()
if (format.version() >= qMakePair(3, 3))
extensions |= QOpenGLExtensions::TextureSwizzle;
+ if (format.version() >= qMakePair(4, 3) || extensionMatcher.match("GL_ARB_invalidate_subdata"))
+ extensions |= QOpenGLExtensions::DiscardFramebuffer;
+
if (extensionMatcher.match("GL_ARB_map_buffer_range"))
extensions |= QOpenGLExtensions::MapBufferRange;
@@ -5047,7 +5055,23 @@ QOpenGLExtensionsPrivate::QOpenGLExtensionsPrivate(QOpenGLContext *ctx)
MapBuffer = RESOLVE(MapBuffer);
GetBufferSubData = RESOLVE(GetBufferSubData);
DiscardFramebuffer = RESOLVE(DiscardFramebuffer);
- }
+}
+
+void QOpenGLExtensions::discardFramebuffer(GLenum target, GLsizei numAttachments, const GLenum *attachments)
+{
+ Q_D(QOpenGLExtensions);
+ Q_ASSERT(QOpenGLExtensions::isInitialized(d));
+ Q_ASSERT(d->f.InvalidateFramebuffer || d->DiscardFramebuffer);
+
+ // On GLES >= 3 we prefer glInvalidateFramebuffer, even if the
+ // discard extension is present
+ if (d->f.InvalidateFramebuffer)
+ d->f.InvalidateFramebuffer(target, numAttachments, attachments);
+ else
+ d->DiscardFramebuffer(target, numAttachments, attachments);
+
+ Q_OPENGL_FUNCTIONS_DEBUG
+}
void QOpenGLExtensions::flushShared()
{
diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp
index f609cddd3c..3b709ec77b 100644
--- a/src/gui/painting/qbackingstore.cpp
+++ b/src/gui/painting/qbackingstore.cpp
@@ -50,6 +50,7 @@ public:
QScopedPointer<QImage> highDpiBackingstore;
QRegion staticContents;
QSize size;
+ QSize nativeSize;
bool downscale = qEnvironmentVariableIntValue("QT_WIDGETS_HIGHDPI_DOWNSCALE") > 0;
};
@@ -115,14 +116,13 @@ QWindow* QBackingStore::window() const
void QBackingStore::beginPaint(const QRegion &region)
{
- const qreal dpr = d_ptr->backingStoreDevicePixelRatio();
+ const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
- if (d_ptr->highDpiBackingstore &&
- d_ptr->highDpiBackingstore->devicePixelRatio() != dpr)
+ if (d_ptr->nativeSize != QHighDpi::scale(size(), toNativeFactor))
resize(size());
QPlatformBackingStore *platformBackingStore = handle();
- platformBackingStore->beginPaint(QHighDpi::scale(region, d_ptr->deviceIndependentToNativeFactor()));
+ platformBackingStore->beginPaint(QHighDpi::scale(region, toNativeFactor));
// When QtGui is applying a high-dpi scale factor the backing store
// creates a "large" backing store image. This image needs to be
@@ -131,18 +131,20 @@ void QBackingStore::beginPaint(const QRegion &region)
// the image data to avoid having the new devicePixelRatio be propagated
// back to the platform plugin.
QPaintDevice *device = platformBackingStore->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
+ if (!qFuzzyCompare(toNativeFactor, qreal(1)) && device->devType() == QInternal::Image) {
QImage *source = static_cast<QImage *>(device);
const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
- || source->data_ptr() != d_ptr->highDpiBackingstore->data_ptr()
+ || source->constBits() != d_ptr->highDpiBackingstore->constBits()
|| source->size() != d_ptr->highDpiBackingstore->size()
- || source->devicePixelRatio() != d_ptr->highDpiBackingstore->devicePixelRatio();
- if (needsNewImage) {
+ || source->bytesPerLine() != d_ptr->highDpiBackingstore->bytesPerLine()
+ || source->format() != d_ptr->highDpiBackingstore->format();
+ if (needsNewImage)
d_ptr->highDpiBackingstore.reset(
new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));
- d_ptr->highDpiBackingstore->setDevicePixelRatio(dpr);
- }
+ d_ptr->highDpiBackingstore->setDevicePixelRatio(d_ptr->backingStoreDevicePixelRatio());
+ } else {
+ d_ptr->highDpiBackingstore.reset();
}
}
@@ -156,7 +158,7 @@ QPaintDevice *QBackingStore::paintDevice()
{
QPaintDevice *device = handle()->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image)
+ if (!qFuzzyCompare(d_ptr->deviceIndependentToNativeFactor(), qreal(1)) && device->devType() == QInternal::Image)
return d_ptr->highDpiBackingstore.data();
return device;
@@ -229,8 +231,10 @@ void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &
*/
void QBackingStore::resize(const QSize &size)
{
+ const qreal factor = d_ptr->deviceIndependentToNativeFactor();
d_ptr->size = size;
- handle()->resize(QHighDpi::scale(size, d_ptr->deviceIndependentToNativeFactor()), d_ptr->staticContents);
+ d_ptr->nativeSize = QHighDpi::scale(size, factor);
+ handle()->resize(d_ptr->nativeSize, QHighDpi::scale(d_ptr->staticContents, factor));
}
/*!
@@ -266,6 +270,13 @@ bool QBackingStore::scroll(const QRegion &area, int dx, int dy)
*/
void QBackingStore::setStaticContents(const QRegion &region)
{
+ [[maybe_unused]] static const bool didCheckPlatformSupport = []{
+ const auto *integration = QGuiApplicationPrivate::platformIntegration();
+ if (!integration->hasCapability(QPlatformIntegration::BackingStoreStaticContents))
+ qWarning("QBackingStore::setStaticContents(): Platform does not support static contents");
+ return true;
+ }();
+
d_ptr->staticContents = region;
}
diff --git a/src/gui/painting/qbackingstoredefaultcompositor.cpp b/src/gui/painting/qbackingstoredefaultcompositor.cpp
index 998e6f7a8f..c1452ca768 100644
--- a/src/gui/painting/qbackingstoredefaultcompositor.cpp
+++ b/src/gui/painting/qbackingstoredefaultcompositor.cpp
@@ -18,18 +18,13 @@ QBackingStoreDefaultCompositor::~QBackingStoreDefaultCompositor()
void QBackingStoreDefaultCompositor::reset()
{
m_rhi = nullptr;
- delete m_psNoBlend;
- m_psNoBlend = nullptr;
- delete m_psBlend;
- m_psBlend = nullptr;
- delete m_psPremulBlend;
- m_psPremulBlend = nullptr;
- delete m_sampler;
- m_sampler = nullptr;
- delete m_vbuf;
- m_vbuf = nullptr;
- delete m_texture;
- m_texture = nullptr;
+ m_psNoBlend.reset();
+ m_psBlend.reset();
+ m_psPremulBlend.reset();
+ m_samplerNearest.reset();
+ m_samplerLinear.reset();
+ m_vbuf.reset();
+ m_texture.reset();
m_widgetQuadData.reset();
for (PerQuadData &d : m_textureQuadData)
d.reset();
@@ -100,7 +95,7 @@ QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QImage &sourceImage
const bool resized = !m_texture || m_texture->pixelSize() != image.size();
if (dirtyRegion.isEmpty() && !resized)
- return m_texture;
+ return m_texture.get();
if (needsConversion)
image = image.convertToFormat(QImage::Format_RGBA8888);
@@ -109,11 +104,11 @@ QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QImage &sourceImage
if (resized) {
if (!m_texture)
- m_texture = rhi->newTexture(QRhiTexture::RGBA8, image.size());
+ m_texture.reset(rhi->newTexture(QRhiTexture::RGBA8, image.size()));
else
m_texture->setPixelSize(image.size());
m_texture->create();
- resourceUpdates->uploadTexture(m_texture, image);
+ resourceUpdates->uploadTexture(m_texture.get(), image);
} else {
QRect imageRect = image.rect();
QRect rect = dirtyRegion.boundingRect() & imageRect;
@@ -122,10 +117,10 @@ QRhiTexture *QBackingStoreDefaultCompositor::toTexture(const QImage &sourceImage
subresDesc.setSourceSize(rect.size());
subresDesc.setDestinationTopLeft(rect.topLeft());
QRhiTextureUploadDescription uploadDesc(QRhiTextureUploadEntry(0, 0, subresDesc));
- resourceUpdates->uploadTexture(m_texture, uploadDesc);
+ resourceUpdates->uploadTexture(m_texture.get(), uploadDesc);
}
- return m_texture;
+ return m_texture.get();
}
static inline QRect scaledRect(const QRect &rect, qreal factor)
@@ -171,7 +166,7 @@ static QMatrix4x4 targetTransform(const QRectF &target, const QRect &viewport, b
matrix(1,3) = y_translate;
matrix(0,0) = x_scale;
- matrix(1,1) = y_scale;
+ matrix(1,1) = (invertY ? -1.0 : 1.0) * y_scale;
return matrix;
}
@@ -280,7 +275,7 @@ enum class PipelineBlend {
static QRhiGraphicsPipeline *createGraphicsPipeline(QRhi *rhi,
QRhiShaderResourceBindings *srb,
- QRhiSwapChain *swapchain,
+ QRhiRenderPassDescriptor *rpDesc,
PipelineBlend blend)
{
QRhiGraphicsPipeline *ps = rhi->newGraphicsPipeline();
@@ -324,7 +319,7 @@ static QRhiGraphicsPipeline *createGraphicsPipeline(QRhi *rhi,
});
ps->setVertexInputLayout(inputLayout);
ps->setShaderResourceBindings(srb);
- ps->setRenderPassDescriptor(swapchain->renderPassDescriptor());
+ ps->setRenderPassDescriptor(rpDesc);
if (!ps->create()) {
qWarning("QBackingStoreDefaultCompositor: Failed to build graphics pipeline");
@@ -347,7 +342,7 @@ QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::crea
d.srb = m_rhi->newShaderResourceBindings();
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
- QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_sampler)
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_samplerNearest.get())
});
if (!d.srb->create())
qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
@@ -357,7 +352,7 @@ QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::crea
d.srbExtra = m_rhi->newShaderResourceBindings();
d.srbExtra->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf, 0, UBUF_SIZE),
- QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_sampler)
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_samplerNearest.get())
});
if (!d.srbExtra->create())
qWarning("QBackingStoreDefaultCompositor: Failed to create srb");
@@ -368,27 +363,31 @@ QBackingStoreDefaultCompositor::PerQuadData QBackingStoreDefaultCompositor::crea
return d;
}
-void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra)
+void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra,
+ UpdateQuadDataOptions options)
{
// This whole check-if-texture-ptr-is-different is needed because there is
// nothing saying a QPlatformTextureList cannot return a different
// QRhiTexture* from the same index in a subsequent flush.
- if (d->lastUsedTexture == texture || !d->srb)
+ const QRhiSampler::Filter filter = options.testFlag(NeedsLinearFiltering) ? QRhiSampler::Linear : QRhiSampler::Nearest;
+ if ((d->lastUsedTexture == texture && d->lastUsedFilter == filter) || !d->srb)
return;
+ QRhiSampler *sampler = filter == QRhiSampler::Linear ? m_samplerLinear.get() : m_samplerNearest.get();
d->srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
- QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, m_sampler)
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, texture, sampler)
});
d->srb->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
d->lastUsedTexture = texture;
+ d->lastUsedFilter = filter;
if (textureExtra) {
d->srbExtra->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d->ubuf, 0, UBUF_SIZE),
- QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, m_sampler)
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, textureExtra, sampler)
});
d->srbExtra->updateResources(QRhiShaderResourceBindings::BindingsAreSorted);
@@ -397,17 +396,18 @@ void QBackingStoreDefaultCompositor::updatePerQuadData(PerQuadData *d, QRhiTextu
}
void QBackingStoreDefaultCompositor::updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
- const QMatrix4x4 &target, const QMatrix3x3 &source, UpdateUniformOption option)
+ const QMatrix4x4 &target, const QMatrix3x3 &source,
+ UpdateUniformOptions options)
{
resourceUpdates->updateDynamicBuffer(d->ubuf, 0, 64, target.constData());
updateMatrix3x3(resourceUpdates, d->ubuf, source);
float opacity = 1.0f;
resourceUpdates->updateDynamicBuffer(d->ubuf, 112, 4, &opacity);
- qint32 textureSwizzle = static_cast<qint32>(option);
+ qint32 textureSwizzle = options;
resourceUpdates->updateDynamicBuffer(d->ubuf, 116, 4, &textureSwizzle);
}
-void QBackingStoreDefaultCompositor::ensureResources(QRhiSwapChain *swapchain, QRhiResourceUpdateBatch *resourceUpdates)
+void QBackingStoreDefaultCompositor::ensureResources(QRhiResourceUpdateBatch *resourceUpdates, QRhiRenderPassDescriptor *rpDesc)
{
static const float vertexData[] = {
-1, -1, 0, 0, 0,
@@ -419,30 +419,37 @@ void QBackingStoreDefaultCompositor::ensureResources(QRhiSwapChain *swapchain, Q
};
if (!m_vbuf) {
- m_vbuf = m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData));
+ m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData)));
if (m_vbuf->create())
- resourceUpdates->uploadStaticBuffer(m_vbuf, vertexData);
+ resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData);
else
qWarning("QBackingStoreDefaultCompositor: Failed to create vertex buffer");
}
- if (!m_sampler) {
- m_sampler = m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
- QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
- if (!m_sampler->create())
- qWarning("QBackingStoreDefaultCompositor: Failed to create sampler");
+ if (!m_samplerNearest) {
+ m_samplerNearest.reset(m_rhi->newSampler(QRhiSampler::Nearest, QRhiSampler::Nearest, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ if (!m_samplerNearest->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create sampler (Nearest filtering)");
+ }
+
+ if (!m_samplerLinear) {
+ m_samplerLinear.reset(m_rhi->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge));
+ if (!m_samplerLinear->create())
+ qWarning("QBackingStoreDefaultCompositor: Failed to create sampler (Linear filtering)");
}
if (!m_widgetQuadData.isValid())
- m_widgetQuadData = createPerQuadData(m_texture);
+ m_widgetQuadData = createPerQuadData(m_texture.get());
QRhiShaderResourceBindings *srb = m_widgetQuadData.srb; // just for the layout
if (!m_psNoBlend)
- m_psNoBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::None);
+ m_psNoBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::None));
if (!m_psBlend)
- m_psBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::Alpha);
+ m_psBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::Alpha));
if (!m_psPremulBlend)
- m_psPremulBlend = createGraphicsPipeline(m_rhi, srb, swapchain, PipelineBlend::PremulAlpha);
+ m_psPremulBlend.reset(createGraphicsPipeline(m_rhi, srb, rpDesc, PipelineBlend::PremulAlpha));
}
QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatformBackingStore *backingStore,
@@ -455,6 +462,9 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
QPlatformTextureList *textures,
bool translucentBackground)
{
+ if (!rhi)
+ return QPlatformBackingStore::FlushFailed;
+
Q_ASSERT(textures); // may be empty if there are no render-to-texture widgets at all, but null it cannot be
if (!m_rhi) {
@@ -508,12 +518,15 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
if (!gotTextureFromGraphicsBuffer)
toTexture(backingStore, rhi, resourceUpdates, scaledRegion(region, sourceDevicePixelRatio, offset), &flags);
- ensureResources(swapchain, resourceUpdates);
+ ensureResources(resourceUpdates, swapchain->renderPassDescriptor());
+ UpdateUniformOptions uniformOptions;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
- const UpdateUniformOption uniformOption = (flags & QPlatformBackingStore::TextureSwizzle) != 0? NeedsRedBlueSwap : NoOption;
+ if (flags & QPlatformBackingStore::TextureSwizzle)
+ uniformOptions |= NeedsRedBlueSwap;
#else
- const UpdateUniformOption uniformOption = (flags & QPlatformBackingStore::TextureSwizzle) != 0? NeedsAlphaRotate : NoOption;
+ if (flags & QPlatformBackingStore::TextureSwizzle)
+ uniformOptions |= NeedsAlphaRotate;
#endif
const bool premultiplied = (flags & QPlatformBackingStore::TexturePremultiplied) != 0;
SourceTransformOrigin origin = SourceTransformOrigin::TopLeft;
@@ -522,21 +535,29 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
const qreal dpr = window->devicePixelRatio();
const QRect deviceWindowRect = scaledRect(QRect(QPoint(), window->size()), dpr);
+ const QRect sourceWindowRect = scaledRect(QRect(QPoint(), window->size()), sourceDevicePixelRatio);
+ // If sourceWindowRect is larger than deviceWindowRect, we are doing high
+ // DPI downscaling. In that case Linear filtering is a must, whereas for the
+ // 1:1 case Nearest must be used for Qt 5 visual compatibility.
+ const bool needsLinearSampler = sourceWindowRect.width() > deviceWindowRect.width()
+ && sourceWindowRect.height() > deviceWindowRect.height();
+
+ const bool invertTargetY = !rhi->isYUpInNDC();
+ const bool invertSource = !rhi->isYUpInFramebuffer();
- const bool invertTargetY = rhi->clipSpaceCorrMatrix().data()[5] < 0.0f;
- const bool invertSource = rhi->isYUpInFramebuffer() != rhi->isYUpInNDC();
if (m_texture) {
// The backingstore is for the entire tlw. In case of native children, offset tells the position
// relative to the tlw. The window rect is scaled by the source device pixel ratio to get
// the source rect.
- const QRect sourceWindowRect = scaledRect(QRect(QPoint(), window->size()), sourceDevicePixelRatio);
const QPoint sourceWindowOffset = scaledOffset(offset, sourceDevicePixelRatio);
const QRect srcRect = toBottomLeftRect(sourceWindowRect.translated(sourceWindowOffset), m_texture->pixelSize().height());
const QMatrix3x3 source = sourceTransform(srcRect, m_texture->pixelSize(), origin);
QMatrix4x4 target; // identity
if (invertTargetY)
target.data()[5] = -1.0f;
- updateUniforms(&m_widgetQuadData, resourceUpdates, target, source, uniformOption);
+ updateUniforms(&m_widgetQuadData, resourceUpdates, target, source, uniformOptions);
+ if (needsLinearSampler)
+ updatePerQuadData(&m_widgetQuadData, m_texture.get(), nullptr, NeedsLinearFiltering);
}
const int textureWidgetCount = textures->count();
@@ -548,10 +569,13 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
}
for (int i = 0; i < textureWidgetCount; ++i) {
+ const bool invertSourceForTextureWidget = textures->flags(i).testFlag(QPlatformTextureList::MirrorVertically)
+ ? !invertSource : invertSource;
QMatrix4x4 target;
QMatrix3x3 source;
if (!prepareDrawForRenderToTextureWidget(textures, i, window, deviceWindowRect,
- offset, invertTargetY, invertSource, &target, &source))
+ offset, invertTargetY, invertSourceForTextureWidget,
+ &target, &source))
{
m_textureQuadData[i].reset();
continue;
@@ -559,13 +583,13 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
QRhiTexture *t = textures->texture(i);
QRhiTexture *tExtra = textures->textureExtra(i);
if (t) {
- if (!m_textureQuadData[i].isValid()) {
+ if (!m_textureQuadData[i].isValid())
m_textureQuadData[i] = createPerQuadData(t, tExtra);
- }
- else {
+ else
updatePerQuadData(&m_textureQuadData[i], t, tExtra);
- }
- updateUniforms(&m_textureQuadData[i], resourceUpdates, target, source, NoOption);
+ updateUniforms(&m_textureQuadData[i], resourceUpdates, target, source);
+ if (needsLinearSampler)
+ updatePerQuadData(&m_textureQuadData[i], t, tExtra, NeedsLinearFiltering);
} else {
m_textureQuadData[i].reset();
}
@@ -587,9 +611,9 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
cb->beginPass(target, clearColor, { 1.0f, 0 });
- cb->setGraphicsPipeline(m_psNoBlend);
+ cb->setGraphicsPipeline(m_psNoBlend.get());
cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
- QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf, 0);
+ QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
cb->setVertexInput(0, 1, &vbufBinding);
// Textures for renderToTexture widgets.
@@ -607,7 +631,7 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
}
}
- cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend : m_psBlend);
+ cb->setGraphicsPipeline(premultiplied ? m_psPremulBlend.get() : m_psBlend.get());
// Backingstore texture with the normal widgets.
if (m_texture) {
@@ -621,9 +645,9 @@ QPlatformBackingStore::FlushResult QBackingStoreDefaultCompositor::flush(QPlatfo
if (flags.testFlag(QPlatformTextureList::StacksOnTop)) {
if (m_textureQuadData[i].isValid()) {
if (flags.testFlag(QPlatformTextureList::NeedsPremultipliedAlphaBlending))
- cb->setGraphicsPipeline(m_psPremulBlend);
+ cb->setGraphicsPipeline(m_psPremulBlend.get());
else
- cb->setGraphicsPipeline(m_psBlend);
+ cb->setGraphicsPipeline(m_psBlend.get());
QRhiShaderResourceBindings* srb = m_textureQuadData[i].srb;
if (buffer == QRhiSwapChain::RightBuffer && m_textureQuadData[i].srbExtra)
diff --git a/src/gui/painting/qbackingstoredefaultcompositor_p.h b/src/gui/painting/qbackingstoredefaultcompositor_p.h
index 839fa61c73..c5a8ffd328 100644
--- a/src/gui/painting/qbackingstoredefaultcompositor_p.h
+++ b/src/gui/painting/qbackingstoredefaultcompositor_p.h
@@ -45,12 +45,16 @@ public:
private:
enum UpdateUniformOption {
- NoOption = 0x00,
- NeedsRedBlueSwap = 0x01,
- NeedsAlphaRotate = 0x02,
+ NeedsRedBlueSwap = 1 << 0,
+ NeedsAlphaRotate = 1 << 1
};
+ Q_DECLARE_FLAGS(UpdateUniformOptions, UpdateUniformOption)
+ enum UpdateQuadDataOption {
+ NeedsLinearFiltering = 1 << 0
+ };
+ Q_DECLARE_FLAGS(UpdateQuadDataOptions, UpdateQuadDataOption)
- void ensureResources(QRhiSwapChain *swapchain, QRhiResourceUpdateBatch *resourceUpdates);
+ void ensureResources(QRhiResourceUpdateBatch *resourceUpdates, QRhiRenderPassDescriptor *rpDesc);
QRhiTexture *toTexture(const QImage &image,
QRhi *rhi,
QRhiResourceUpdateBatch *resourceUpdates,
@@ -58,13 +62,14 @@ private:
QPlatformBackingStore::TextureFlags *flags) const;
mutable QRhi *m_rhi = nullptr;
- mutable QRhiTexture *m_texture = nullptr;
+ mutable std::unique_ptr<QRhiTexture> m_texture;
- QRhiBuffer *m_vbuf = nullptr;
- QRhiSampler *m_sampler = nullptr;
- QRhiGraphicsPipeline *m_psNoBlend = nullptr;
- QRhiGraphicsPipeline *m_psBlend = nullptr;
- QRhiGraphicsPipeline *m_psPremulBlend = nullptr;
+ std::unique_ptr<QRhiBuffer> m_vbuf;
+ std::unique_ptr<QRhiSampler> m_samplerNearest;
+ std::unique_ptr<QRhiSampler> m_samplerLinear;
+ std::unique_ptr<QRhiGraphicsPipeline> m_psNoBlend;
+ std::unique_ptr<QRhiGraphicsPipeline> m_psBlend;
+ std::unique_ptr<QRhiGraphicsPipeline> m_psPremulBlend;
struct PerQuadData {
QRhiBuffer *ubuf = nullptr;
@@ -73,6 +78,7 @@ private:
QRhiShaderResourceBindings *srbExtra = nullptr; // may be null (used for stereo)
QRhiTexture *lastUsedTexture = nullptr;
QRhiTexture *lastUsedTextureExtra = nullptr; // may be null (used for stereo)
+ QRhiSampler::Filter lastUsedFilter = QRhiSampler::None;
bool isValid() const { return ubuf && srb; }
void reset() {
delete ubuf;
@@ -85,15 +91,18 @@ private:
}
lastUsedTexture = nullptr;
lastUsedTextureExtra = nullptr;
+ lastUsedFilter = QRhiSampler::None;
}
};
PerQuadData m_widgetQuadData;
QVarLengthArray<PerQuadData, 8> m_textureQuadData;
PerQuadData createPerQuadData(QRhiTexture *texture, QRhiTexture *textureExtra = nullptr);
- void updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra = nullptr);
+ void updatePerQuadData(PerQuadData *d, QRhiTexture *texture, QRhiTexture *textureExtra = nullptr,
+ UpdateQuadDataOptions options = {});
void updateUniforms(PerQuadData *d, QRhiResourceUpdateBatch *resourceUpdates,
- const QMatrix4x4 &target, const QMatrix3x3 &source, UpdateUniformOption option);
+ const QMatrix4x4 &target, const QMatrix3x3 &source,
+ UpdateUniformOptions options = {});
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qbackingstorerhisupport.cpp b/src/gui/painting/qbackingstorerhisupport.cpp
index 15fea26d16..37c52155eb 100644
--- a/src/gui/painting/qbackingstorerhisupport.cpp
+++ b/src/gui/painting/qbackingstorerhisupport.cpp
@@ -56,6 +56,19 @@ bool QBackingStoreRhiSupport::create()
QOffscreenSurface *surface = nullptr;
QRhi::Flags flags;
+ // These must be the same env.vars Qt Quick uses (as documented), in order
+ // to ensure symmetry in the behavior between a QQuickWindow and a
+ // (QRhi-based) widget top-level window.
+ if (qEnvironmentVariableIntValue("QSG_RHI_PREFER_SOFTWARE_RENDERER"))
+ flags |= QRhi::PreferSoftwareRenderer;
+ if (qEnvironmentVariableIntValue("QSG_RHI_PROFILE"))
+ flags |= QRhi::EnableDebugMarkers | QRhi::EnableTimestamps;
+
+ if (m_config.api() == QPlatformBackingStoreRhiConfig::Null) {
+ QRhiNullInitParams params;
+ rhi = QRhi::create(QRhi::Null, &params, flags);
+ }
+
#if QT_CONFIG(opengl)
if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::OpenGL) {
surface = QRhiGles2InitParams::newFallbackSurface(m_format);
@@ -75,7 +88,7 @@ bool QBackingStoreRhiSupport::create()
params.enableDebugLayer = m_config.isDebugLayerEnabled();
rhi = QRhi::create(QRhi::D3D11, &params, flags);
if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
- qCDebug(lcQpaBackingStore, "Failed to create a D3D device with default settings; "
+ qCDebug(lcQpaBackingStore, "Failed to create a D3D11 device with default settings; "
"attempting to get a software rasterizer backed device instead");
flags |= QRhi::PreferSoftwareRenderer;
rhi = QRhi::create(QRhi::D3D11, &params, flags);
@@ -84,19 +97,25 @@ bool QBackingStoreRhiSupport::create()
QRhiD3D12InitParams params;
params.enableDebugLayer = m_config.isDebugLayerEnabled();
rhi = QRhi::create(QRhi::D3D12, &params, flags);
+ if (!rhi && !flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+ qCDebug(lcQpaBackingStore, "Failed to create a D3D12 device with default settings; "
+ "attempting to get a software rasterizer backed device instead");
+ flags |= QRhi::PreferSoftwareRenderer;
+ rhi = QRhi::create(QRhi::D3D12, &params, flags);
+ }
}
}
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
if (!rhi && m_config.api() == QPlatformBackingStoreRhiConfig::Metal) {
QRhiMetalInitParams params;
// For parity with Qt Quick, fall back to OpenGL when there is no Metal (f.ex. in macOS virtual machines).
if (QRhi::probe(QRhi::Metal, &params)) {
rhi = QRhi::create(QRhi::Metal, &params, flags);
} else {
- qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL.");
- rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
+ qCDebug(lcQpaBackingStore, "Metal does not seem to be supported");
+ return false;
}
}
#endif
@@ -177,13 +196,14 @@ QRhiSwapChain *QBackingStoreRhiSupport::swapChainForWindow(QWindow *window)
bool QBackingStoreRhiSupportWindowWatcher::eventFilter(QObject *obj, QEvent *event)
{
- if (event->type() == QEvent::PlatformSurface
- && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
+ if (event->type() == QEvent::WindowAboutToChangeInternal
+ || (event->type() == QEvent::PlatformSurface
+ && static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed))
{
QWindow *window = qobject_cast<QWindow *>(obj);
auto it = m_rhiSupport->m_swapchains.find(window);
if (it != m_rhiSupport->m_swapchains.end()) {
- qCDebug(lcQpaBackingStore) << "SurfaceAboutToBeDestroyed received for tracked window" << window << "cleaning up swapchain";
+ qCDebug(lcQpaBackingStore) << event << "received for" << window << "- cleaning up swapchain";
auto data = *it;
m_rhiSupport->m_swapchains.erase(it);
data.reset(); // deletes 'this'
@@ -252,7 +272,7 @@ bool QBackingStoreRhiSupport::checkForceRhi(QPlatformBackingStoreRhiConfig *outC
if (config.isEnabled()) {
#if defined(Q_OS_WIN)
config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
-#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#elif QT_CONFIG(metal)
config.setApi(QPlatformBackingStoreRhiConfig::Metal);
#elif QT_CONFIG(opengl)
config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
@@ -272,7 +292,7 @@ bool QBackingStoreRhiSupport::checkForceRhi(QPlatformBackingStoreRhiConfig *outC
if (backend == QStringLiteral("d3d12"))
config.setApi(QPlatformBackingStoreRhiConfig::D3D12);
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
if (backend == QStringLiteral("metal"))
config.setApi(QPlatformBackingStoreRhiConfig::Metal);
#endif
diff --git a/src/gui/painting/qbezier.cpp b/src/gui/painting/qbezier.cpp
index 35d4a3a664..5b2a962661 100644
--- a/src/gui/painting/qbezier.cpp
+++ b/src/gui/painting/qbezier.cpp
@@ -134,108 +134,6 @@ void QBezier::addToPolygon(QDataBuffer<QPointF> &polygon, qreal bezier_flattenin
}
}
-QPolygonF QBezier::toQuadratics(qreal errorLimit) const
-{
- qreal infPoints[2];
- int numInfPoints = inflectionPoints(infPoints);
- QPolygonF res;
- res.reserve((numInfPoints + 1) * 3 * 2);
- res.append(pt1());
- qreal t0 = 0;
- for (int i = 0; i < numInfPoints + 1; i++) { // #segments == #inflectionpoints + 1
- qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
- QBezier segment = bezierOnInterval(t0, t1);
- segment.addToQuadratics(&res, t1 - t0, errorLimit);
- t0 = t1;
- }
- return res;
-}
-
-static inline qreal scoreQuadratic(const QBezier &b, QPointF qcp)
-{
- // Construct a cubic from the quadratic, and compare its control points to the originals'
- const QRectF bounds = b.bounds();
- qreal dim = QLineF(bounds.topLeft(), bounds.bottomRight()).length();
- if (qFuzzyIsNull(dim))
- return 1;
- const qreal f = 2.0 / 3;
- const QPointF cp1 = b.pt1() + f * (qcp - b.pt1());
- const QPointF cp2 = b.pt4() + f * (qcp - b.pt4());
- const QLineF d1(b.pt2(), cp1);
- const QLineF d2(b.pt3(), cp2);
- return qMax(d1.length(), d2.length()) / dim;
-}
-
-static inline QPointF quadraticForCubic(const QBezier &b)
-{
- QPointF qcp;
- const QLineF st = b.startTangent();
- const QLineF et = b.endTangent();
- if (st.intersects(et, &qcp) == QLineF::NoIntersection)
- qcp = b.midPoint();
- return qcp;
-}
-
-void QBezier::addToQuadratics(QPolygonF *p, qreal tspan, qreal errorLimit) const
-{
- Q_ASSERT((tspan > 0) && !(tspan > 1));
- static constexpr qreal MinimumTSpan = 0.1;
-
- QPointF qcp = quadraticForCubic(*this);
- if (tspan < MinimumTSpan || scoreQuadratic(*this, qcp) < errorLimit) {
- p->append(qcp);
- p->append(pt4());
- } else {
- std::pair<QBezier, QBezier> halves = split();
- halves.first.addToQuadratics(p, tspan / 2, errorLimit);
- halves.second.addToQuadratics(p, tspan / 2, errorLimit);
- }
-}
-
-int QBezier::inflectionPoints(qreal *tpoints) const
-{
- auto isValidRoot = [](qreal r) {
- return qIsFinite(r) && (r > 0) && (!qFuzzyIsNull(float(r))) && (r < 1)
- && (!qFuzzyIsNull(float(r - 1)));
- };
-
- // normalize so pt1.x,pt1.y,pt4.y == 0
- QTransform xf;
- const QLineF l(pt1(), pt4());
- xf.rotate(l.angle());
- xf.translate(-pt1().x(), -pt1().y());
- const QBezier n = mapBy(xf);
- Q_ASSERT(n.pt1() == QPoint() && qFuzzyIsNull(float(n.pt4().y())));
-
- const qreal p = n.pt3().x() * n.pt2().y();
- const qreal q = n.pt4().x() * n.pt2().y();
- const qreal r = n.pt2().x() * n.pt3().y();
- const qreal s = n.pt4().x() * n.pt3().y();
-
- const qreal a = 36 * ((-3 * p) + (2 * q) + (3 * r) - s);
- if (!a)
- return 0;
- const qreal b = -18 * (((3 * p) - q) - (3 * r));
- const qreal c = 18 * (r - p);
- const qreal rad = (b * b) - (2 * a * c);
- if (rad < 0)
- return 0;
- const qreal sqr = qSqrt(rad);
- const qreal root1 = (b + sqr) / a;
- const qreal root2 = (b - sqr) / a;
-
- int res = 0;
- if (isValidRoot(root1))
- tpoints[res++] = root1;
- if (!qFuzzyCompare(root2, root1) && isValidRoot(root2))
- tpoints[res++] = root2;
-
- if (res == 2 && tpoints[0] > tpoints[1])
- qSwap(tpoints[0], tpoints[1]);
-
- return res;
-}
-
QRectF QBezier::bounds() const
{
qreal xmin = x1;
diff --git a/src/gui/painting/qbezier_p.h b/src/gui/painting/qbezier_p.h
index c1b868263a..6c39dd55f9 100644
--- a/src/gui/painting/qbezier_p.h
+++ b/src/gui/painting/qbezier_p.h
@@ -18,7 +18,6 @@
#include <QtGui/private/qtguiglobal_p.h>
#include "QtCore/qline.h"
#include "QtCore/qlist.h"
-#include "QtCore/qpair.h"
#include "QtCore/qpoint.h"
#include "QtCore/qrect.h"
#include "QtGui/qtransform.h"
@@ -47,10 +46,6 @@ public:
void addToPolygon(QPolygonF *p, qreal bezier_flattening_threshold = 0.5) const;
void addToPolygon(QDataBuffer<QPointF> &polygon, qreal bezier_flattening_threshold) const;
- QPolygonF toQuadratics(qreal errorLimit = 0.2) const;
- void addToQuadratics(QPolygonF *p, qreal tspan = 1.0, qreal errorLimit = 0.2) const;
- int inflectionPoints(qreal *tpoints) const;
-
QRectF bounds() const;
qreal length(qreal error = 0.01) const;
void addIfClose(qreal *length, qreal error) const;
diff --git a/src/gui/painting/qblendfunctions.cpp b/src/gui/painting/qblendfunctions.cpp
index 2a51d06204..fa9f89262c 100644
--- a/src/gui/painting/qblendfunctions.cpp
+++ b/src/gui/painting/qblendfunctions.cpp
@@ -152,7 +152,7 @@ void qt_blend_rgb16_on_rgb16(uchar *dst, int dbpl,
if (const_alpha == 256) {
int length = w << 1;
- while (h--) {
+ while (--h >= 0) {
memcpy(dst, src, length);
dst += dbpl;
src += sbpl;
@@ -162,7 +162,7 @@ void qt_blend_rgb16_on_rgb16(uchar *dst, int dbpl,
const quint16 *s = (const quint16 *) src;
quint8 a = (255 * const_alpha) >> 8;
quint8 ia = 255 - a;
- while (h--) {
+ while (--h >= 0) {
for (int x=0; x<w; ++x) {
d[x] = BYTE_MUL_RGB16(s[x], a) + BYTE_MUL_RGB16(d[x], ia);
}
diff --git a/src/gui/painting/qblendfunctions_p.h b/src/gui/painting/qblendfunctions_p.h
index 90a7dbcf64..2f5ed36c73 100644
--- a/src/gui/painting/qblendfunctions_p.h
+++ b/src/gui/painting/qblendfunctions_p.h
@@ -89,7 +89,7 @@ void qt_scale_image_16bit(uchar *destPixels, int dbpl,
if (xend < 0 || xend >= (int)(sbpl/sizeof(SRC)))
--w;
- while (h--) {
+ while (--h >= 0) {
const SRC *src = (const SRC *) (srcPixels + (srcy >> 16) * sbpl);
quint32 srcx = basex;
int x = 0;
@@ -180,7 +180,7 @@ template <typename T> void qt_scale_image_32bit(uchar *destPixels, int dbpl,
if (xend < 0 || xend >= (int)(sbpl/sizeof(quint32)))
--w;
- while (h--) {
+ while (--h >= 0) {
const uint *src = (const quint32 *) (srcPixels + (srcy >> 16) * sbpl);
quint32 srcx = basex;
int x = 0;
diff --git a/src/gui/painting/qcmyk_p.h b/src/gui/painting/qcmyk_p.h
new file mode 100644
index 0000000000..d00a4b5a6e
--- /dev/null
+++ b/src/gui/painting/qcmyk_p.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCMYK_P_H
+#define QCMYK_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 <QtGui/private/qtguiglobal_p.h>
+#include <QtGui/qcolor.h>
+
+QT_BEGIN_NAMESPACE
+
+class QCmyk32
+{
+private:
+ uint m_cmyk = 0;
+
+public:
+ QCmyk32() = default;
+
+ constexpr QCmyk32(int cyan, int magenta, int yellow, int black) :
+#if QT_BYTE_ORDER == Q_BIG_ENDIAN
+ m_cmyk(cyan << 24 | magenta << 16 | yellow << 8 | black)
+#else
+ m_cmyk(cyan | magenta << 8 | yellow << 16 | black << 24)
+#endif
+ {
+ }
+
+#if QT_BYTE_ORDER == Q_BIG_ENDIAN
+ constexpr int cyan() const noexcept { return (m_cmyk >> 24) & 0xff; }
+ constexpr int magenta() const noexcept { return (m_cmyk >> 16) & 0xff; }
+ constexpr int yellow() const noexcept { return (m_cmyk >> 8) & 0xff; }
+ constexpr int black() const noexcept { return (m_cmyk ) & 0xff; }
+#else
+ constexpr int cyan() const noexcept { return (m_cmyk ) & 0xff; }
+ constexpr int magenta() const noexcept { return (m_cmyk >> 8) & 0xff; }
+ constexpr int yellow() const noexcept { return (m_cmyk >> 16) & 0xff; }
+ constexpr int black() const noexcept { return (m_cmyk >> 24) & 0xff; }
+#endif
+
+ QColor toColor() const noexcept
+ {
+ return QColor::fromCmyk(cyan(), magenta(), yellow(), black());
+ }
+
+ constexpr uint toUint() const noexcept
+ {
+ return m_cmyk;
+ }
+
+ constexpr static QCmyk32 fromCmyk32(uint cmyk) noexcept
+ {
+ QCmyk32 result;
+ result.m_cmyk = cmyk;
+ return result;
+ }
+
+ static QCmyk32 fromRgba(QRgb rgba) noexcept
+ {
+ const QColor c = QColor(rgba).toCmyk();
+ return QCmyk32(c.cyan(), c.magenta(), c.yellow(), c.black());
+ }
+
+ static QCmyk32 fromColor(const QColor &color) noexcept
+ {
+ QColor c = color.toCmyk();
+ return QCmyk32(c.cyan(), c.magenta(), c.yellow(), c.black());
+ }
+};
+
+static_assert(sizeof(QCmyk32) == sizeof(int));
+static_assert(alignof(QCmyk32) == alignof(int));
+static_assert(std::is_standard_layout_v<QCmyk32>);
+
+QT_END_NAMESPACE
+
+#endif // QCMYK_P_H
diff --git a/src/gui/painting/qcolorclut_p.h b/src/gui/painting/qcolorclut_p.h
new file mode 100644
index 0000000000..d95e9701b7
--- /dev/null
+++ b/src/gui/painting/qcolorclut_p.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOLORCLUT_H
+#define QCOLORCLUT_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 <QtCore/qlist.h>
+#include <QtGui/private/qcolormatrix_p.h>
+
+QT_BEGIN_NAMESPACE
+
+// A 3/4-dimensional lookup table compatible with ICC lut8, lut16, mAB, and mBA formats.
+class QColorCLUT
+{
+ inline static QColorVector interpolate(const QColorVector &a, const QColorVector &b, float t)
+ {
+ return a + (b - a) * t; // faster than std::lerp by assuming no super large or non-number floats
+ }
+ inline static void interpolateIn(QColorVector &a, const QColorVector &b, float t)
+ {
+ a += (b - a) * t;
+ }
+public:
+ uint32_t gridPointsX = 0;
+ uint32_t gridPointsY = 0;
+ uint32_t gridPointsZ = 0;
+ uint32_t gridPointsW = 1;
+ QList<QColorVector> table;
+
+ bool isEmpty() const { return table.isEmpty(); }
+
+ QColorVector apply(const QColorVector &v) const
+ {
+ Q_ASSERT(table.size() == gridPointsX * gridPointsY * gridPointsZ * gridPointsW);
+ QColorVector frac;
+ const float x = std::clamp(v.x, 0.0f, 1.0f) * (gridPointsX - 1);
+ const float y = std::clamp(v.y, 0.0f, 1.0f) * (gridPointsY - 1);
+ const float z = std::clamp(v.z, 0.0f, 1.0f) * (gridPointsZ - 1);
+ const float w = std::clamp(v.w, 0.0f, 1.0f) * (gridPointsW - 1);
+ const uint32_t lox = static_cast<uint32_t>(std::floor(x));
+ const uint32_t hix = std::min(lox + 1, gridPointsX - 1);
+ const uint32_t loy = static_cast<uint32_t>(std::floor(y));
+ const uint32_t hiy = std::min(loy + 1, gridPointsY - 1);
+ const uint32_t loz = static_cast<uint32_t>(std::floor(z));
+ const uint32_t hiz = std::min(loz + 1, gridPointsZ - 1);
+ const uint32_t low = static_cast<uint32_t>(std::floor(w));
+ const uint32_t hiw = std::min(low + 1, gridPointsW - 1);
+ frac.x = x - static_cast<float>(lox);
+ frac.y = y - static_cast<float>(loy);
+ frac.z = z - static_cast<float>(loz);
+ frac.w = w - static_cast<float>(low);
+ if (gridPointsW > 1) {
+ auto index = [&](qsizetype x, qsizetype y, qsizetype z, qsizetype w) -> qsizetype {
+ return x * gridPointsW * gridPointsZ * gridPointsY
+ + y * gridPointsW * gridPointsZ
+ + z * gridPointsW
+ + w;
+ };
+ QColorVector tmp[8];
+ // interpolate over w
+ tmp[0] = interpolate(table[index(lox, loy, loz, low)],
+ table[index(lox, loy, loz, hiw)], frac.w);
+ tmp[1] = interpolate(table[index(lox, loy, hiz, low)],
+ table[index(lox, loy, hiz, hiw)], frac.w);
+ tmp[2] = interpolate(table[index(lox, hiy, loz, low)],
+ table[index(lox, hiy, loz, hiw)], frac.w);
+ tmp[3] = interpolate(table[index(lox, hiy, hiz, low)],
+ table[index(lox, hiy, hiz, hiw)], frac.w);
+ tmp[4] = interpolate(table[index(hix, loy, loz, low)],
+ table[index(hix, loy, loz, hiw)], frac.w);
+ tmp[5] = interpolate(table[index(hix, loy, hiz, low)],
+ table[index(hix, loy, hiz, hiw)], frac.w);
+ tmp[6] = interpolate(table[index(hix, hiy, loz, low)],
+ table[index(hix, hiy, loz, hiw)], frac.w);
+ tmp[7] = interpolate(table[index(hix, hiy, hiz, low)],
+ table[index(hix, hiy, hiz, hiw)], frac.w);
+ // interpolate over z
+ for (int i = 0; i < 4; ++i)
+ interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
+ // interpolate over y
+ for (int i = 0; i < 2; ++i)
+ interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
+ // interpolate over x
+ interpolateIn(tmp[0], tmp[4], frac.x);
+ return tmp[0];
+ }
+ auto index = [&](qsizetype x, qsizetype y, qsizetype z) -> qsizetype {
+ return x * gridPointsZ * gridPointsY
+ + y * gridPointsZ
+ + z;
+ };
+ QColorVector tmp[8] = {
+ table[index(lox, loy, loz)],
+ table[index(lox, loy, hiz)],
+ table[index(lox, hiy, loz)],
+ table[index(lox, hiy, hiz)],
+ table[index(hix, loy, loz)],
+ table[index(hix, loy, hiz)],
+ table[index(hix, hiy, loz)],
+ table[index(hix, hiy, hiz)]
+ };
+ // interpolate over z
+ for (int i = 0; i < 4; ++i)
+ interpolateIn(tmp[i * 2], tmp[i * 2 + 1], frac.z);
+ // interpolate over y
+ for (int i = 0; i < 2; ++i)
+ interpolateIn(tmp[i * 4], tmp[i * 4 + 2], frac.y);
+ // interpolate over x
+ interpolateIn(tmp[0], tmp[4], frac.x);
+ return tmp[0];
+ }
+};
+
+QT_END_NAMESPACE
+
+#endif // QCOLORCLUT_H
diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h
index 1ce5df6264..6a9b9f4f9a 100644
--- a/src/gui/painting/qcolormatrix_p.h
+++ b/src/gui/painting/qcolormatrix_p.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QCOLORMATRIX_H
@@ -18,6 +18,7 @@
#include <QtGui/qtguiglobal.h>
#include <QtCore/qpoint.h>
#include <QtCore/private/qglobal_p.h>
+#include <QtCore/private/qsimd_p.h>
#include <cmath>
QT_BEGIN_NAMESPACE
@@ -27,25 +28,24 @@ class QColorVector
{
public:
QColorVector() = default;
- constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z) { }
- explicit constexpr QColorVector(const QPointF &chr) // from XY chromaticity
- : x(chr.x() / chr.y())
- , y(1.0f)
- , z((1.0 - chr.x() - chr.y()) / chr.y())
- { }
- float x = 0.0f; // X, x or red
- float y = 0.0f; // Y, y or green
- float z = 0.0f; // Z, Y or blue
- float _unused = 0.0f;
+ constexpr QColorVector(float x, float y, float z, float w = 0.0f) noexcept : x(x), y(y), z(z), w(w) { }
+ static constexpr QColorVector fromXYChromaticity(QPointF chr)
+ { return {float(chr.x() / chr.y()), 1.0f, float((1.0f - chr.x() - chr.y()) / chr.y())}; }
+ float x = 0.0f; // X, x, L, or red/cyan
+ float y = 0.0f; // Y, y, a, or green/magenta
+ float z = 0.0f; // Z, Y, b, or blue/yellow
+ float w = 0.0f; // unused, or black
- friend inline bool operator==(const QColorVector &v1, const QColorVector &v2);
- friend inline bool operator!=(const QColorVector &v1, const QColorVector &v2);
- bool isNull() const
+ constexpr bool isNull() const noexcept
{
- return !x && !y && !z;
+ return !x && !y && !z && !w;
+ }
+ bool isValid() const noexcept
+ {
+ return std::isfinite(x) && std::isfinite(y) && std::isfinite(z);
}
- static bool isValidChromaticity(const QPointF &chr)
+ static constexpr bool isValidChromaticity(const QPointF &chr)
{
if (chr.x() < qreal(0.0) || chr.x() > qreal(1.0))
return false;
@@ -56,26 +56,149 @@ public:
return true;
}
+ constexpr QColorVector operator*(float f) const { return QColorVector(x * f, y * f, z * f, w * f); }
+ constexpr QColorVector operator+(const QColorVector &v) const { return QColorVector(x + v.x, y + v.y, z + v.z, w + v.w); }
+ constexpr QColorVector operator-(const QColorVector &v) const { return QColorVector(x - v.x, y - v.y, z - v.z, w - v.w); }
+ void operator+=(const QColorVector &v) { x += v.x; y += v.y; z += v.z; w += v.w; }
+
+ QPointF toChromaticity() const
+ {
+ if (isNull())
+ return QPointF();
+ float mag = 1.0f / (x + y + z);
+ return QPointF(x * mag, y * mag);
+ }
+
// Common whitepoints:
static constexpr QPointF D50Chromaticity() { return QPointF(0.34567, 0.35850); }
static constexpr QPointF D65Chromaticity() { return QPointF(0.31271, 0.32902); }
- static constexpr QColorVector D50() { return QColorVector(D50Chromaticity()); }
- static constexpr QColorVector D65() { return QColorVector(D65Chromaticity()); }
+ static constexpr QColorVector D50() { return fromXYChromaticity(D50Chromaticity()); }
+ static constexpr QColorVector D65() { return fromXYChromaticity(D65Chromaticity()); }
+
+ QColorVector xyzToLab() const
+ {
+ constexpr QColorVector ref = D50();
+ constexpr float eps = 0.008856f;
+ constexpr float kap = 903.3f;
+#if defined(__SSE2__)
+ const __m128 iref = _mm_setr_ps(1.f / ref.x, 1.f / ref.y, 1.f / ref.z, 0.f);
+ __m128 v = _mm_loadu_ps(&x);
+ v = _mm_mul_ps(v, iref);
+
+ const __m128 f3 = _mm_set1_ps(3.f);
+ __m128 est = _mm_add_ps(_mm_set1_ps(0.25f), _mm_mul_ps(v, _mm_set1_ps(0.75f))); // float est = 0.25f + (x * 0.75f);
+ __m128 estsq = _mm_mul_ps(est, est);
+ est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
+ _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
+ estsq = _mm_mul_ps(est, est);
+ est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
+ _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
+ estsq = _mm_mul_ps(est, est);
+ est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
+ _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
+ estsq = _mm_mul_ps(est, est);
+ est = _mm_sub_ps(est, _mm_mul_ps(_mm_sub_ps(_mm_mul_ps(estsq, est), v),
+ _mm_rcp_ps(_mm_mul_ps(estsq, f3)))); // est -= ((est * est * est) - x) / (3.f * (est * est));
+
+ __m128 kapmul = _mm_mul_ps(_mm_add_ps(_mm_mul_ps(v, _mm_set1_ps(kap)), _mm_set1_ps(16.f)),
+ _mm_set1_ps(1.f / 116.f)); // f_ = (kap * f_ + 16.f) * (1.f / 116.f);
+ __m128 cmpgt = _mm_cmpgt_ps(v, _mm_set1_ps(eps)); // if (f_ > eps)
+#if defined(__SSE4_1__)
+ v = _mm_blendv_ps(kapmul, est, cmpgt); // if (..) f_ =.. else f_ =..
+#else
+ v = _mm_or_ps(_mm_and_ps(cmpgt, est), _mm_andnot_ps(cmpgt, kapmul));
+#endif
+ alignas(16) float out[4];
+ _mm_store_ps(out, v);
+ const float L = 116.f * out[1] - 16.f;
+ const float a = 500.f * (out[0] - out[1]);
+ const float b = 200.f * (out[1] - out[2]);
+#else
+ float xr = x * (1.f / ref.x);
+ float yr = y * (1.f / ref.y);
+ float zr = z * (1.f / ref.z);
+
+ float fx, fy, fz;
+ if (xr > eps)
+ fx = fastCbrt(xr);
+ else
+ fx = (kap * xr + 16.f) * (1.f / 116.f);
+ if (yr > eps)
+ fy = fastCbrt(yr);
+ else
+ fy = (kap * yr + 16.f) * (1.f / 116.f);
+ if (zr > eps)
+ fz = fastCbrt(zr);
+ else
+ fz = (kap * zr + 16.f) * (1.f / 116.f);
+
+ const float L = 116.f * fy - 16.f;
+ const float a = 500.f * (fx - fy);
+ const float b = 200.f * (fy - fz);
+#endif
+ // We output Lab values that has been scaled to 0.0->1.0 values, see also labToXyz.
+ return QColorVector(L * (1.f / 100.f), (a + 128.f) * (1.f / 255.f), (b + 128.f) * (1.f / 255.f));
+ }
+
+ QColorVector labToXyz() const
+ {
+ constexpr QColorVector ref = D50();
+ constexpr float eps = 0.008856f;
+ constexpr float kap = 903.3f;
+ // This transform has been guessed from the ICC spec, but it is not stated
+ // anywhere to be the one to use to map to and from 0.0->1.0 values:
+ const float L = x * 100.f;
+ const float a = (y * 255.f) - 128.f;
+ const float b = (z * 255.f) - 128.f;
+ // From here is official Lab->XYZ conversion:
+ float fy = (L + 16.f) * (1.f / 116.f);
+ float fx = fy + (a * (1.f / 500.f));
+ float fz = fy - (b * (1.f / 200.f));
+
+ float xr, yr, zr;
+ if (fx * fx * fx > eps)
+ xr = fx * fx * fx;
+ else
+ xr = (116.f * fx - 16) * (1.f / kap);
+ if (L > (kap * eps))
+ yr = fy * fy * fy;
+ else
+ yr = L * (1.f / kap);
+ if (fz * fz * fz > eps)
+ zr = fz * fz * fz;
+ else
+ zr = (116.f * fz - 16) * (1.f / kap);
+
+ xr = xr * ref.x;
+ yr = yr * ref.y;
+ zr = zr * ref.z;
+ return QColorVector(xr, yr, zr);
+ }
+ friend inline bool comparesEqual(const QColorVector &lhs, const QColorVector &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QColorVector);
+
+private:
+ static float fastCbrt(float x)
+ {
+ // This gives us cube root within the precision we need.
+ float est = 0.25f + (x * 0.75f); // guessing a cube-root of numbers between 0.01 and 1.
+ est -= ((est * est * est) - x) / (3.f * (est * est));
+ est -= ((est * est * est) - x) / (3.f * (est * est));
+ est -= ((est * est * est) - x) / (3.f * (est * est));
+ est -= ((est * est * est) - x) / (3.f * (est * est));
+ // Q_ASSERT(qAbs(est - std::cbrt(x)) < 0.0001f);
+ return est;
+ }
};
-inline bool operator==(const QColorVector &v1, const QColorVector &v2)
+inline bool comparesEqual(const QColorVector &v1, const QColorVector &v2)
{
return (std::abs(v1.x - v2.x) < (1.0f / 2048.0f))
&& (std::abs(v1.y - v2.y) < (1.0f / 2048.0f))
- && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f));
+ && (std::abs(v1.z - v2.z) < (1.0f / 2048.0f))
+ && (std::abs(v1.w - v2.w) < (1.0f / 2048.0f));
}
-inline bool operator!=(const QColorVector &v1, const QColorVector &v2)
-{
- return !(v1 == v2);
-}
-
-
// A matrix mapping 3 value colors.
// Not using QTransform because only floats are needed and performance is critical.
class QColorMatrix
@@ -86,20 +209,20 @@ public:
QColorVector g;
QColorVector b;
- friend inline bool operator==(const QColorMatrix &m1, const QColorMatrix &m2);
- friend inline bool operator!=(const QColorMatrix &m1, const QColorMatrix &m2);
-
- bool isNull() const
+ constexpr bool isNull() const
{
return r.isNull() && g.isNull() && b.isNull();
}
+ constexpr float determinant() const
+ {
+ return r.x * (b.z * g.y - g.z * b.y) -
+ r.y * (b.z * g.x - g.z * b.x) +
+ r.z * (b.y * g.x - g.y * b.x);
+ }
bool isValid() const
{
// A color matrix must be invertible
- float det = r.x * (b.z * g.y - g.z * b.y) -
- r.y * (b.z * g.x - g.z * b.x) +
- r.z * (b.y * g.x - g.y * b.x);
- return !qFuzzyIsNull(det);
+ return std::isnormal(determinant());
}
bool isIdentity() const noexcept
{
@@ -108,9 +231,7 @@ public:
QColorMatrix inverted() const
{
- float det = r.x * (b.z * g.y - g.z * b.y) -
- r.y * (b.z * g.x - g.z * b.x) +
- r.z * (b.y * g.x - g.y * b.x);
+ float det = determinant();
det = 1.0f / det;
QColorMatrix inv;
inv.r.x = (g.y * b.z - b.y * g.z) * det;
@@ -124,20 +245,20 @@ public:
inv.b.z = (r.x * g.y - g.x * r.y) * det;
return inv;
}
- QColorMatrix operator*(const QColorMatrix &o) const
+ friend inline constexpr QColorMatrix operator*(const QColorMatrix &a, const QColorMatrix &o)
{
QColorMatrix comb;
- comb.r.x = r.x * o.r.x + g.x * o.r.y + b.x * o.r.z;
- comb.g.x = r.x * o.g.x + g.x * o.g.y + b.x * o.g.z;
- comb.b.x = r.x * o.b.x + g.x * o.b.y + b.x * o.b.z;
+ comb.r.x = a.r.x * o.r.x + a.g.x * o.r.y + a.b.x * o.r.z;
+ comb.g.x = a.r.x * o.g.x + a.g.x * o.g.y + a.b.x * o.g.z;
+ comb.b.x = a.r.x * o.b.x + a.g.x * o.b.y + a.b.x * o.b.z;
- comb.r.y = r.y * o.r.x + g.y * o.r.y + b.y * o.r.z;
- comb.g.y = r.y * o.g.x + g.y * o.g.y + b.y * o.g.z;
- comb.b.y = r.y * o.b.x + g.y * o.b.y + b.y * o.b.z;
+ comb.r.y = a.r.y * o.r.x + a.g.y * o.r.y + a.b.y * o.r.z;
+ comb.g.y = a.r.y * o.g.x + a.g.y * o.g.y + a.b.y * o.g.z;
+ comb.b.y = a.r.y * o.b.x + a.g.y * o.b.y + a.b.y * o.b.z;
- comb.r.z = r.z * o.r.x + g.z * o.r.y + b.z * o.r.z;
- comb.g.z = r.z * o.g.x + g.z * o.g.y + b.z * o.g.z;
- comb.b.z = r.z * o.b.x + g.z * o.b.y + b.z * o.b.z;
+ comb.r.z = a.r.z * o.r.x + a.g.z * o.r.y + a.b.z * o.r.z;
+ comb.g.z = a.r.z * o.g.x + a.g.z * o.g.y + a.b.z * o.g.z;
+ comb.b.z = a.r.z * o.b.x + a.g.z * o.b.y + a.b.z * o.b.z;
return comb;
}
@@ -164,6 +285,32 @@ public:
{ 0.0f, v.y, 0.0f },
{ 0.0f, 0.0f, v.z } };
}
+ static QColorMatrix chromaticAdaptation(const QColorVector &whitePoint)
+ {
+ constexpr QColorVector whitePointD50 = QColorVector::D50();
+ if (whitePoint != whitePointD50) {
+ // A chromatic adaptation to map a white point to XYZ D50.
+
+ // The Bradford method chromatic adaptation matrix:
+ const QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
+ { 0.2664f, 1.7135f, -0.0685f },
+ { -0.1614f, 0.0367f, 1.0296f } };
+ const QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
+ { -0.1470543f, 0.5183603f, 0.0400428f },
+ { 0.1599627f, 0.0492912f, 0.9684867f } };
+
+ const QColorVector srcCone = abrad.map(whitePoint);
+ if (srcCone.x && srcCone.y && srcCone.z) {
+ const QColorVector dstCone = abrad.map(whitePointD50);
+ const QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
+ { 0, dstCone.y / srcCone.y, 0 },
+ { 0, 0, dstCone.z / srcCone.z } };
+ return abradinv * (wToD50 * abrad);
+ }
+ }
+ return QColorMatrix::identity();
+ }
+
// These are used to recognize matrices from ICC profiles:
static QColorMatrix toXyzFromSRgb()
{
@@ -189,18 +336,15 @@ public:
{ 0.1351922452f, 0.7118769884f, 0.0000000000f },
{ 0.0313525312f, 0.0000856627f, 0.8251883388f } };
}
+ friend inline bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QColorMatrix);
};
-inline bool operator==(const QColorMatrix &m1, const QColorMatrix &m2)
+inline bool comparesEqual(const QColorMatrix &m1, const QColorMatrix &m2)
{
return (m1.r == m2.r) && (m1.g == m2.g) && (m1.b == m2.b);
}
-inline bool operator!=(const QColorMatrix &m1, const QColorMatrix &m2)
-{
- return !(m1 == m2);
-}
-
QT_END_NAMESPACE
#endif // QCOLORMATRIX_P_H
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp
index 487c295298..f21e6ec738 100644
--- a/src/gui/painting/qcolorspace.cpp
+++ b/src/gui/painting/qcolorspace.cpp
@@ -5,6 +5,7 @@
#include "qcolorspace_p.h"
#include "qcolortransform.h"
+#include "qcolorclut_p.h"
#include "qcolormatrix_p.h"
#include "qcolortransferfunction_p.h"
#include "qcolortransform_p.h"
@@ -80,49 +81,19 @@ bool QColorSpacePrimaries::areValid() const
QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
{
// This converts to XYZ in some undefined scale.
- QColorMatrix toXyz = { QColorVector(redPoint),
- QColorVector(greenPoint),
- QColorVector(bluePoint) };
+ QColorMatrix toXyz = { QColorVector::fromXYChromaticity(redPoint),
+ QColorVector::fromXYChromaticity(greenPoint),
+ QColorVector::fromXYChromaticity(bluePoint) };
// Since the white point should be (1.0, 1.0, 1.0) in the
// input, we can figure out the scale by using the
// inverse conversion on the white point.
- QColorVector wXyz(whitePoint);
+ const auto wXyz = QColorVector::fromXYChromaticity(whitePoint);
QColorVector whiteScale = toXyz.inverted().map(wXyz);
// Now we have scaled conversion to XYZ relative to the given whitepoint
toXyz = toXyz * QColorMatrix::fromScale(whiteScale);
- // But we want a conversion to XYZ relative to D50
- QColorVector wXyzD50 = QColorVector::D50();
-
- if (wXyz != wXyzD50) {
- // Do chromatic adaptation to map our white point to XYZ D50.
-
- // The Bradford method chromatic adaptation matrix:
- QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
- { 0.2664f, 1.7135f, -0.0685f },
- { -0.1614f, 0.0367f, 1.0296f } };
- QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
- { -0.1470543f, 0.5183603f, 0.0400428f },
- { 0.1599627f, 0.0492912f, 0.9684867f } };
-
- QColorVector srcCone = abrad.map(wXyz);
- QColorVector dstCone = abrad.map(wXyzD50);
-
- if (srcCone.x && srcCone.y && srcCone.z) {
- QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
- { 0, dstCone.y / srcCone.y, 0 },
- { 0, 0, dstCone.z / srcCone.z } };
-
-
- QColorMatrix chromaticAdaptation = abradinv * (wToD50 * abrad);
- toXyz = chromaticAdaptation * toXyz;
- } else {
- toXyz.r = {0, 0, 0}; // set to invalid value
- }
- }
-
return toXyz;
}
@@ -132,6 +103,7 @@ QColorSpacePrivate::QColorSpacePrivate()
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace)
: namedColorSpace(namedColorSpace)
+ , colorModel(QColorSpace::ColorModel::Rgb)
{
switch (namedColorSpace) {
case QColorSpace::SRgb:
@@ -169,6 +141,7 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma)
: primaries(primaries)
, transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(gamma)
{
identifyColorSpace();
@@ -180,18 +153,50 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
float gamma)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(gamma)
+ , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint))
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
+
identifyColorSpace();
setTransferFunction();
}
+QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint,
+ QColorSpace::TransferFunction transferFunction,
+ float gamma)
+ : primaries(QColorSpace::Primaries::Custom)
+ , transferFunction(transferFunction)
+ , colorModel(QColorSpace::ColorModel::Gray)
+ , gamma(gamma)
+ , whitePoint(QColorVector::fromXYChromaticity(whitePoint))
+{
+ chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ toXyz = chad;
+ setTransferFunction();
+}
+
+QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
+ : primaries(QColorSpace::Primaries::Custom)
+ , transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Gray)
+ , gamma(0)
+ , whitePoint(QColorVector::fromXYChromaticity(whitePoint))
+{
+ chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
+ toXyz = chad;
+ setTransferFunctionTable(transferFunctionTable);
+ setTransferFunction();
+}
+
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QList<uint16_t> &transferFunctionTable)
: primaries(primaries)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
{
setTransferFunctionTable(transferFunctionTable);
@@ -202,11 +207,14 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const Q
QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QList<uint16_t> &transferFunctionTable)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
+ , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint))
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
@@ -218,16 +226,18 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
const QList<uint16_t> &blueTransferFunctionTable)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
+ , colorModel(QColorSpace::ColorModel::Rgb)
, gamma(0)
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
- whitePoint = QColorVector(primaries.whitePoint);
+ whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
setTransferFunctionTables(redTransferFunctionTable,
greenTransferFunctionTable,
blueTransferFunctionTable);
identifyColorSpace();
- setToXyzMatrix();
}
void QColorSpacePrivate::identifyColorSpace()
@@ -304,7 +314,9 @@ void QColorSpacePrivate::setToXyzMatrix()
}
QColorSpacePrimaries colorSpacePrimaries(primaries);
toXyz = colorSpacePrimaries.toXyzMatrix();
- whitePoint = QColorVector(colorSpacePrimaries.whitePoint);
+ whitePoint = QColorVector::fromXYChromaticity(colorSpacePrimaries.whitePoint);
+ chad = QColorMatrix::chromaticAdaptation(whitePoint);
+ toXyz = chad * toXyz;
}
void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transferFunctionTable)
@@ -319,7 +331,7 @@ void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transfe
QColorTransferFunction curve;
if (table.asColorTransferFunction(&curve)) {
// Table recognized as a specific curve
- if (curve.isLinear()) {
+ if (curve.isIdentity()) {
transferFunction = QColorSpace::TransferFunction::Linear;
gamma = 1.0f;
} else if (curve.isSRgb()) {
@@ -418,7 +430,12 @@ QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpace
combined.d = ptr;
ptr->colorSpaceIn = this;
ptr->colorSpaceOut = out;
- ptr->colorMatrix = out->toXyz.inverted() * toXyz;
+ if (isThreeComponentMatrix())
+ ptr->colorMatrix = toXyz;
+ else
+ ptr->colorMatrix = QColorMatrix::identity();
+ if (out->isThreeComponentMatrix())
+ ptr->colorMatrix = out->toXyz.inverted() * ptr->colorMatrix;
if (ptr->isIdentity())
return QColorTransform();
return combined;
@@ -431,10 +448,36 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
transform.d = ptr;
ptr->colorSpaceIn = this;
ptr->colorSpaceOut = this;
- ptr->colorMatrix = toXyz;
+ if (isThreeComponentMatrix())
+ ptr->colorMatrix = toXyz;
+ else
+ ptr->colorMatrix = QColorMatrix::identity();
+ // Convert to XYZ relative to our white point, not the regular D50 white point.
+ if (!chad.isNull())
+ ptr->colorMatrix = chad.inverted() * ptr->colorMatrix;
+ else if (!whitePoint.isNull())
+ ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * ptr->colorMatrix;
return transform;
}
+bool QColorSpacePrivate::isThreeComponentMatrix() const
+{
+ return transformModel == QColorSpace::TransformModel::ThreeComponentMatrix;
+}
+
+void QColorSpacePrivate::clearElementListProcessingForEdit()
+{
+ Q_ASSERT(transformModel == QColorSpace::TransformModel::ElementListProcessing);
+ Q_ASSERT(primaries == QColorSpace::Primaries::Custom);
+ Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom);
+
+ transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
+ colorModel = QColorSpace::ColorModel::Rgb;
+ isPcsLab = false;
+ mAB.clear();
+ mBA.clear();
+}
+
/*!
\class QColorSpace
\brief The QColorSpace class provides a color space abstraction.
@@ -457,9 +500,10 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
A color space can generally speaking be conceived as a combination of set of primary
colors and a transfer function. The primaries defines the axes of the color space, and
the transfer function how values are mapped on the axes.
- The primaries are defined by three primary colors that represent exactly how red, green,
- and blue look in this particular color space, and a white color that represents where
- and how bright pure white is. The range of colors expressible by the primary colors is
+ The primaries are for ColorModel::Rgb color spaces defined by three primary colors that
+ represent exactly how red, green, and blue look in this particular color space, and a white
+ color that represents where and how bright pure white is. For grayscale color spaces, only
+ a single white primary is needed. The range of colors expressible by the primary colors is
called the gamut, and a color space that can represent a wider range of colors is also
known as a wide-gamut color space.
@@ -513,6 +557,35 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
*/
/*!
+ \enum QColorSpace::TransformModel
+ \since 6.8
+
+ Defines the processing model used for color space transforms.
+
+ \value ThreeComponentMatrix The transform consist of a matrix calculated from primaries and set of transfer functions
+ for each color channel. This is very fast and used by all predefined color spaces. Any color space on this form is
+ reversible and always both valid sources and targets.
+ \value ElementListProcessing The transforms are one or two lists of processing elements that can do many things,
+ each list only process either to the connection color space or from it. This is very flexible, but rather
+ slow, and can only be set by reading ICC profiles (See \l fromIccProfile()). Since the two lists are
+ separate a color space on this form can be a valid source, but not necessarily also a valid target. When changing
+ either primaries or transfer function on a color space on this type it will reset to an empty ThreeComponentMatrix form.
+*/
+
+/*!
+ \enum QColorSpace::ColorModel
+ \since 6.8
+
+ Defines the color model used by the color space data.
+
+ \value Undefined No color model
+ \value Rgb An RGB color model with red, green, and blue colors. Can apply to RGB and grayscale data.
+ \value Gray A gray scale color model. Can only apply to grayscale data.
+ \value Cmyk Can only represent color data defined with cyan, magenta, yellow, and black colors.
+ In effect only QImage::Format_CMYK32. Note Cmyk color spaces will be TransformModel::ElementListProcessing.
+*/
+
+/*!
\fn QColorSpace::QColorSpace()
Creates a new colorspace object that represents an undefined and invalid colorspace.
@@ -575,6 +648,28 @@ QColorSpace::QColorSpace(QColorSpace::Primaries gamut, const QList<uint16_t> &tr
}
/*!
+ Creates a custom grayscale color space with the white point \a whitePoint, using the transfer function \a transferFunction and
+ optionally \a gamma.
+
+ \since 6.8
+*/
+QColorSpace::QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma)
+ : d_ptr(new QColorSpacePrivate(whitePoint, transferFunction, gamma))
+{
+}
+
+/*!
+ Creates a custom grayscale color space with white point \a whitePoint, and using the custom transfer function described by
+ \a transferFunctionTable.
+
+ \since 6.8
+*/
+QColorSpace::QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable)
+ : d_ptr(new QColorSpacePrivate(whitePoint, transferFunctionTable))
+{
+}
+
+/*!
Creates a custom colorspace with a primaries based on the chromaticities of the primary colors \a whitePoint,
\a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma.
*/
@@ -689,7 +784,10 @@ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunc
if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma)
return;
detach();
- d_ptr->description.clear();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
d_ptr->transferFunction = transferFunction;
d_ptr->gamma = gamma;
d_ptr->identifyColorSpace();
@@ -710,7 +808,10 @@ void QColorSpace::setTransferFunction(const QList<uint16_t> &transferFunctionTab
return;
}
detach();
- d_ptr->description.clear();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
d_ptr->setTransferFunctionTable(transferFunctionTable);
d_ptr->gamma = 0;
d_ptr->identifyColorSpace();
@@ -737,7 +838,10 @@ void QColorSpace::setTransferFunctions(const QList<uint16_t> &redTransferFunctio
return;
}
detach();
- d_ptr->description.clear();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
d_ptr->setTransferFunctionTables(redTransferFunctionTable,
greenTransferFunctionTable,
blueTransferFunctionTable);
@@ -753,7 +857,7 @@ void QColorSpace::setTransferFunctions(const QList<uint16_t> &redTransferFunctio
*/
QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) const
{
- if (!isValid() || transferFunction == QColorSpace::TransferFunction::Custom)
+ if (!isValid() || transferFunction == TransferFunction::Custom)
return *this;
if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma)
return *this;
@@ -813,8 +917,12 @@ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId)
if (d_ptr->primaries == primariesId)
return;
detach();
- d_ptr->description.clear();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
d_ptr->primaries = primariesId;
+ d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
d_ptr->identifyColorSpace();
d_ptr->setToXyzMatrix();
}
@@ -836,17 +944,100 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
return;
}
QColorMatrix toXyz = primaries.toXyzMatrix();
- if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz)
+ QColorMatrix chad = QColorMatrix::chromaticAdaptation(QColorVector::fromXYChromaticity(whitePoint));
+ toXyz = chad * toXyz;
+ if (QColorVector::fromXYChromaticity(primaries.whitePoint) == d_ptr->whitePoint
+ && toXyz == d_ptr->toXyz && chad == d_ptr->chad)
return;
detach();
- d_ptr->description.clear();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
d_ptr->primaries = QColorSpace::Primaries::Custom;
+ d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
d_ptr->toXyz = toXyz;
- d_ptr->whitePoint = QColorVector(primaries.whitePoint);
+ d_ptr->chad = chad;
+ d_ptr->whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint);
+ d_ptr->identifyColorSpace();
+}
+
+/*!
+ Returns the white point used for this color space. Returns a null QPointF if not defined.
+
+ \since 6.8
+*/
+QPointF QColorSpace::whitePoint() const
+{
+ if (Q_UNLIKELY(!d_ptr))
+ return QPointF();
+ return d_ptr->whitePoint.toChromaticity();
+}
+
+/*!
+ Sets the white point to used for this color space to \a whitePoint.
+
+ \since 6.8
+*/
+void QColorSpace::setWhitePoint(const QPointF &whitePoint)
+{
+ if (Q_UNLIKELY(!d_ptr)) {
+ d_ptr = new QColorSpacePrivate(whitePoint, TransferFunction::Custom, 0.0f);
+ return;
+ }
+ if (QColorVector::fromXYChromaticity(whitePoint) == d_ptr->whitePoint)
+ return;
+ detach();
+ if (d_ptr->transformModel == TransformModel::ElementListProcessing)
+ d_ptr->clearElementListProcessingForEdit();
+ d_ptr->iccProfile = {};
+ d_ptr->description = QString();
+ d_ptr->primaries = QColorSpace::Primaries::Custom;
+ // An RGB color model stays RGB, a gray stays gray, but an undefined one can now be considered gray
+ if (d_ptr->colorModel == QColorSpace::ColorModel::Undefined)
+ d_ptr->colorModel = QColorSpace::ColorModel::Gray;
+ QColorVector wXyz(QColorVector::fromXYChromaticity(whitePoint));
+ if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) {
+ if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) {
+ // Rescale toXyz to new whitepoint
+ QColorMatrix rawToXyz = d_ptr->chad.inverted() * d_ptr->toXyz;
+ QColorVector whiteScale = rawToXyz.inverted().map(wXyz);
+ rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale);
+ d_ptr->chad = QColorMatrix::chromaticAdaptation(wXyz);
+ d_ptr->toXyz = d_ptr->chad * rawToXyz;
+ } else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) {
+ d_ptr->chad = d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz);
+ }
+ }
+ d_ptr->whitePoint = wXyz;
d_ptr->identifyColorSpace();
}
/*!
+ Returns the transfrom processing model used for this color space.
+
+ \since 6.8
+*/
+QColorSpace::TransformModel QColorSpace::transformModel() const noexcept
+{
+ if (Q_UNLIKELY(!d_ptr))
+ return QColorSpace::TransformModel::ThreeComponentMatrix;
+ return d_ptr->transformModel;
+}
+
+/*!
+ Returns the color model this color space can represent
+
+ \since 6.8
+*/
+QColorSpace::ColorModel QColorSpace::colorModel() const noexcept
+{
+ if (Q_UNLIKELY(!d_ptr))
+ return QColorSpace::ColorModel::Undefined;
+ return d_ptr->colorModel;
+}
+
+/*!
\internal
*/
void QColorSpace::detach()
@@ -884,7 +1075,7 @@ QByteArray QColorSpace::iccProfile() const
Creates a QColorSpace from ICC profile \a iccProfile.
\note Not all ICC profiles are supported. QColorSpace only supports
- RGB-XYZ ICC profiles that are three-component matrix-based.
+ RGB or Gray ICC profiles.
If the ICC profile is not supported an invalid QColorSpace is returned
where you can still read the original ICC profile using iccProfile().
@@ -902,13 +1093,51 @@ QColorSpace QColorSpace::fromIccProfile(const QByteArray &iccProfile)
}
/*!
- Returns \c true if the color space is valid.
+ Returns \c true if the color space is valid. For a color space with \c TransformModel::ThreeComponentMatrix
+ that means both primaries and transfer functions set, and implies isValidTarget().
+ For a color space with \c TransformModel::ElementListProcessing it means it has a valid source transform, to
+ check if it also a valid target color space use isValidTarget().
+
+ \sa isValidTarget()
*/
bool QColorSpace::isValid() const noexcept
{
- return d_ptr
- && d_ptr->toXyz.isValid()
- && d_ptr->trc[0].isValid() && d_ptr->trc[1].isValid() && d_ptr->trc[2].isValid();
+ if (!d_ptr)
+ return false;
+ return d_ptr->isValid();
+}
+
+/*!
+ \since 6.8
+
+ Returns \c true if the color space is a valid target color space.
+*/
+bool QColorSpace::isValidTarget() const noexcept
+{
+ if (!d_ptr)
+ return false;
+ if (!d_ptr->isThreeComponentMatrix())
+ return !d_ptr->mBA.isEmpty();
+ return d_ptr->isValid();
+}
+
+/*!
+ \internal
+*/
+bool QColorSpacePrivate::isValid() const noexcept
+{
+ if (!isThreeComponentMatrix())
+ return !mAB.isEmpty();
+ if (!toXyz.isValid())
+ return false;
+ if (colorModel == QColorSpace::ColorModel::Gray) {
+ if (!trc[0].isValid())
+ return false;
+ } else {
+ if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
+ return false;
+ }
+ return true;
}
/*!
@@ -925,6 +1154,53 @@ bool QColorSpace::isValid() const noexcept
otherwise returns \c false
*/
+static bool compareElement(const QColorSpacePrivate::TransferElement &element,
+ const QColorSpacePrivate::TransferElement &other)
+{
+ return element.trc[0] == other.trc[0]
+ && element.trc[1] == other.trc[1]
+ && element.trc[2] == other.trc[2]
+ && element.trc[3] == other.trc[3];
+}
+
+static bool compareElement(const QColorMatrix &element,
+ const QColorMatrix &other)
+{
+ return element == other;
+}
+
+static bool compareElement(const QColorVector &element,
+ const QColorVector &other)
+{
+ return element == other;
+}
+
+static bool compareElement(const QColorCLUT &element,
+ const QColorCLUT &other)
+{
+ if (element.gridPointsX != other.gridPointsX)
+ return false;
+ if (element.gridPointsY != other.gridPointsY)
+ return false;
+ if (element.gridPointsZ != other.gridPointsZ)
+ return false;
+ if (element.gridPointsW != other.gridPointsW)
+ return false;
+ if (element.table.size() != other.table.size())
+ return false;
+ for (qsizetype i = 0; i < element.table.size(); ++i) {
+ if (element.table[i] != other.table[i])
+ return false;
+ }
+ return true;
+}
+
+template<typename T>
+static bool compareElements(const T &element, const QColorSpacePrivate::Element &other)
+{
+ return compareElement(element, std::get<T>(other));
+}
+
/*!
\internal
*/
@@ -932,43 +1208,89 @@ bool QColorSpace::equals(const QColorSpace &other) const
{
if (d_ptr == other.d_ptr)
return true;
- if (!d_ptr || !other.d_ptr)
+ if (!d_ptr)
return false;
+ return d_ptr->equals(other.d_ptr.constData());
+}
- if (d_ptr->namedColorSpace && other.d_ptr->namedColorSpace)
- return d_ptr->namedColorSpace == other.d_ptr->namedColorSpace;
+/*!
+ \internal
+*/
+bool QColorSpacePrivate::equals(const QColorSpacePrivate *other) const
+{
+ if (!other)
+ return false;
+
+ if (namedColorSpace && other->namedColorSpace)
+ return namedColorSpace == other->namedColorSpace;
const bool valid1 = isValid();
- const bool valid2 = other.isValid();
+ const bool valid2 = other->isValid();
if (valid1 != valid2)
return false;
if (!valid1 && !valid2) {
- if (!d_ptr->iccProfile.isEmpty() || !other.d_ptr->iccProfile.isEmpty())
- return d_ptr->iccProfile == other.d_ptr->iccProfile;
+ if (!iccProfile.isEmpty() || !other->iccProfile.isEmpty())
+ return iccProfile == other->iccProfile;
+ return false;
}
// At this point one or both color spaces are unknown, and must be compared in detail instead
- if (primaries() != QColorSpace::Primaries::Custom && other.primaries() != QColorSpace::Primaries::Custom) {
- if (primaries() != other.primaries())
+ if (transformModel != other->transformModel)
+ return false;
+
+ if (!isThreeComponentMatrix()) {
+ if (isPcsLab != other->isPcsLab)
+ return false;
+ if (colorModel != other->colorModel)
+ return false;
+ if (mAB.count() != other->mAB.count())
+ return false;
+ if (mBA.count() != other->mBA.count())
+ return false;
+
+ // Compare element types
+ for (qsizetype i = 0; i < mAB.count(); ++i) {
+ if (mAB[i].index() != other->mAB[i].index())
+ return false;
+ }
+ for (qsizetype i = 0; i < mBA.count(); ++i) {
+ if (mBA[i].index() != other->mBA[i].index())
+ return false;
+ }
+
+ // Compare element contents
+ for (qsizetype i = 0; i < mAB.count(); ++i) {
+ if (!std::visit([&](auto &&elm) { return compareElements(elm, other->mAB[i]); }, mAB[i]))
+ return false;
+ }
+ for (qsizetype i = 0; i < mBA.count(); ++i) {
+ if (!std::visit([&](auto &&elm) { return compareElements(elm, other->mBA[i]); }, mBA[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ if (primaries != QColorSpace::Primaries::Custom && other->primaries != QColorSpace::Primaries::Custom) {
+ if (primaries != other->primaries)
return false;
} else {
- if (d_ptr->toXyz != other.d_ptr->toXyz)
+ if (toXyz != other->toXyz)
return false;
}
- if (transferFunction() != QColorSpace::TransferFunction::Custom &&
- other.transferFunction() != QColorSpace::TransferFunction::Custom) {
- if (transferFunction() != other.transferFunction())
+ if (transferFunction != QColorSpace::TransferFunction::Custom && other->transferFunction != QColorSpace::TransferFunction::Custom) {
+ if (transferFunction != other->transferFunction)
return false;
- if (transferFunction() == QColorSpace::TransferFunction::Gamma)
- return (qAbs(gamma() - other.gamma()) <= (1.0f / 512.0f));
+ if (transferFunction == QColorSpace::TransferFunction::Gamma)
+ return (qAbs(gamma - other->gamma) <= (1.0f / 512.0f));
return true;
}
- if (d_ptr->trc[0] != other.d_ptr->trc[0] ||
- d_ptr->trc[1] != other.d_ptr->trc[1] ||
- d_ptr->trc[2] != other.d_ptr->trc[2])
+ if (trc[0] != other->trc[0] ||
+ trc[1] != other->trc[1] ||
+ trc[2] != other->trc[2])
return false;
return true;
@@ -980,11 +1302,15 @@ bool QColorSpace::equals(const QColorSpace &other) const
*/
QColorTransform QColorSpace::transformationToColorSpace(const QColorSpace &colorspace) const
{
- if (!isValid() || !colorspace.isValid())
+ if (!isValid())
return QColorTransform();
if (*this == colorspace)
return QColorTransform();
+ if (!colorspace.isValidTarget()) {
+ qWarning() << "QColorSpace::transformationToColorSpace: colorspace not a valid target";
+ return QColorTransform();
+ }
return d_ptr->transformationToColorSpace(colorspace.d_ptr.get());
}
@@ -1024,6 +1350,7 @@ QString QColorSpace::description() const noexcept
void QColorSpace::setDescription(const QString &description)
{
detach();
+ d_ptr->iccProfile = {};
d_ptr->userDescription = description;
}
@@ -1066,6 +1393,28 @@ QDataStream &operator>>(QDataStream &s, QColorSpace &colorSpace)
#endif // QT_NO_DATASTREAM
#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QColorSpacePrivate::TransferElement &)
+{
+ return dbg << ":Transfer";
+}
+QDebug operator<<(QDebug dbg, const QColorMatrix &)
+{
+ return dbg << ":Matrix";
+}
+QDebug operator<<(QDebug dbg, const QColorVector &)
+{
+ return dbg << ":Offset";
+}
+QDebug operator<<(QDebug dbg, const QColorCLUT &)
+{
+ return dbg << ":CLUT";
+}
+QDebug operator<<(QDebug dbg, const QList<QColorSpacePrivate::Element> &elements)
+{
+ for (auto &&element : elements)
+ std::visit([&](auto &&elm) { dbg << elm; }, element);
+ return dbg;
+}
QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
{
QDebugStateSaver saver(dbg);
@@ -1074,8 +1423,22 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
if (colorSpace.d_ptr) {
if (colorSpace.d_ptr->namedColorSpace)
dbg << colorSpace.d_ptr->namedColorSpace << ", ";
- dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
- dbg << ", gamma=" << colorSpace.gamma();
+ if (!colorSpace.isValid()) {
+ dbg << "Invalid";
+ if (!colorSpace.d_ptr->iccProfile.isEmpty())
+ dbg << " with profile data";
+ } else if (colorSpace.d_ptr->isThreeComponentMatrix()) {
+ dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
+ dbg << ", gamma=" << colorSpace.gamma();
+ } else {
+ if (colorSpace.d_ptr->isPcsLab)
+ dbg << "PCSLab, ";
+ else
+ dbg << "PCSXYZ, ";
+ dbg << "A2B" << colorSpace.d_ptr->mAB;
+ if (!colorSpace.d_ptr->mBA.isEmpty())
+ dbg << ", B2A" << colorSpace.d_ptr->mBA;
+ }
}
dbg << ')';
return dbg;
diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h
index 4fb5c4273f..488cbd6a53 100644
--- a/src/gui/painting/qcolorspace.h
+++ b/src/gui/painting/qcolorspace.h
@@ -45,9 +45,23 @@ public:
ProPhotoRgb
};
Q_ENUM(TransferFunction)
+ enum class TransformModel : uint8_t {
+ ThreeComponentMatrix = 0,
+ ElementListProcessing,
+ };
+ Q_ENUM(TransformModel)
+ enum class ColorModel : uint8_t {
+ Undefined = 0,
+ Rgb = 1,
+ Gray = 2,
+ Cmyk = 3,
+ };
+ Q_ENUM(ColorModel)
QColorSpace() noexcept = default;
QColorSpace(NamedColorSpace namedColorSpace);
+ QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma = 0.0f);
+ QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
QColorSpace(Primaries primaries, TransferFunction transferFunction, float gamma = 0.0f);
QColorSpace(Primaries primaries, float gamma);
QColorSpace(Primaries primaries, const QList<uint16_t> &transferFunctionTable);
@@ -99,9 +113,14 @@ public:
void setPrimaries(Primaries primariesId);
void setPrimaries(const QPointF &whitePoint, const QPointF &redPoint,
const QPointF &greenPoint, const QPointF &bluePoint);
+ void setWhitePoint(const QPointF &whitePoint);
+ QPointF whitePoint() const;
+ TransformModel transformModel() const noexcept;
+ ColorModel colorModel() const noexcept;
void detach();
bool isValid() const noexcept;
+ bool isValidTarget() const noexcept;
friend inline bool operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2)
{ return colorSpace1.equals(colorSpace2); }
diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h
index 39d901ecdf..4ec801b16b 100644
--- a/src/gui/painting/qcolorspace_p.h
+++ b/src/gui/painting/qcolorspace_p.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QCOLORSPACE_P_H
@@ -16,6 +16,7 @@
//
#include "qcolorspace.h"
+#include "qcolorclut_p.h"
#include "qcolormatrix_p.h"
#include "qcolortrc_p.h"
#include "qcolortrclut_p.h"
@@ -65,6 +66,8 @@ public:
const QList<uint16_t> &redTransferFunctionTable,
const QList<uint16_t> &greenTransferFunctionTable,
const QList<uint16_t> &blueRransferFunctionTable);
+ QColorSpacePrivate(const QPointF &whitePoint, QColorSpace::TransferFunction transferFunction, float gamma);
+ QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable);
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
static const QColorSpacePrivate *get(const QColorSpace &colorSpace)
@@ -77,6 +80,9 @@ public:
return colorSpace.d_ptr.get();
}
+ bool equals(const QColorSpacePrivate *other) const;
+ bool isValid() const noexcept;
+
void initialize();
void setToXyzMatrix();
void setTransferFunction();
@@ -88,21 +94,39 @@ public:
QColorTransform transformationToColorSpace(const QColorSpacePrivate *out) const;
QColorTransform transformationToXYZ() const;
+ bool isThreeComponentMatrix() const;
+ void clearElementListProcessingForEdit();
+
static constexpr QColorSpace::NamedColorSpace Unknown = QColorSpace::NamedColorSpace(0);
QColorSpace::NamedColorSpace namedColorSpace = Unknown;
QColorSpace::Primaries primaries = QColorSpace::Primaries::Custom;
QColorSpace::TransferFunction transferFunction = QColorSpace::TransferFunction::Custom;
+ QColorSpace::TransformModel transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
+ QColorSpace::ColorModel colorModel = QColorSpace::ColorModel::Undefined;
float gamma = 0.0f;
QColorVector whitePoint;
+ // Three component matrix data:
QColorTrc trc[3];
QColorMatrix toXyz;
-
+ QColorMatrix chad;
+
+ // Element list processing data:
+ struct TransferElement {
+ QColorTrc trc[4];
+ };
+ using Element = std::variant<TransferElement, QColorMatrix, QColorVector, QColorCLUT>;
+ bool isPcsLab = false;
+ // A = device, B = PCS
+ QList<Element> mAB, mBA;
+
+ // Metadata
QString description;
QString userDescription;
QByteArray iccProfile;
+ // Cached tables for three component matrix transform:
Q_CONSTINIT static QBasicMutex s_lutWriteLock;
struct LUT {
LUT() = default;
diff --git a/src/gui/painting/qcolortransferfunction_p.h b/src/gui/painting/qcolortransferfunction_p.h
index a9bb26df59..484cc69114 100644
--- a/src/gui/painting/qcolortransferfunction_p.h
+++ b/src/gui/painting/qcolortransferfunction_p.h
@@ -16,44 +16,52 @@
//
#include <QtGui/private/qtguiglobal_p.h>
+#include <QtCore/QFlags>
#include <cmath>
QT_BEGIN_NAMESPACE
// Defines a ICC parametric curve type 4
-class Q_GUI_EXPORT QColorTransferFunction
+class QColorTransferFunction
{
public:
QColorTransferFunction() noexcept
- : m_a(1.0f), m_b(0.0f), m_c(1.0f), m_d(0.0f), m_e(0.0f), m_f(0.0f), m_g(1.0f), m_flags(0)
+ : m_a(1.0f), m_b(0.0f), m_c(1.0f), m_d(0.0f), m_e(0.0f), m_f(0.0f), m_g(1.0f)
+ , m_flags(Hints(Hint::Calculated) | Hint::IsGamma | Hint::IsIdentity)
{ }
+
QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g) noexcept
- : m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags(0)
+ : m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags()
{ }
bool isGamma() const
{
updateHints();
- return m_flags & quint32(Hints::IsGamma);
+ return m_flags & Hint::IsGamma;
}
- bool isLinear() const
+ bool isIdentity() const
{
updateHints();
- return m_flags & quint32(Hints::IsLinear);
+ return m_flags & Hint::IsIdentity;
}
bool isSRgb() const
{
updateHints();
- return m_flags & quint32(Hints::IsSRgb);
+ return m_flags & Hint::IsSRgb;
}
float apply(float x) const
{
if (x < m_d)
return m_c * x + m_f;
+ float t = std::pow(m_a * x + m_b, m_g);
+ if (std::isfinite(t))
+ return t + m_e;
+ if (t > 0.f)
+ return 1.f;
else
- return std::pow(m_a * x + m_b, m_g) + m_e;
+ return 0.f;
}
QColorTransferFunction inverted() const
@@ -62,7 +70,7 @@ public:
d = m_c * m_d + m_f;
- if (!qFuzzyIsNull(m_c)) {
+ if (std::isnormal(m_c)) {
c = 1.0f / m_c;
f = -m_f / m_c;
} else {
@@ -70,8 +78,12 @@ public:
f = 0.0f;
}
- if (!qFuzzyIsNull(m_a) && !qFuzzyIsNull(m_g)) {
+ bool valid_abeg = std::isnormal(m_a) && std::isnormal(m_g);
+ if (valid_abeg)
a = std::pow(1.0f / m_a, m_g);
+ if (valid_abeg && !std::isfinite(a))
+ valid_abeg = false;
+ if (valid_abeg) {
b = -a * m_e;
e = -m_b / m_a;
g = 1.0f / m_g;
@@ -88,15 +100,19 @@ public:
// A few predefined curves:
static QColorTransferFunction fromGamma(float gamma)
{
- return QColorTransferFunction(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, gamma);
+ return QColorTransferFunction(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, gamma,
+ Hints(Hint::Calculated) | Hint::IsGamma |
+ (paramCompare(gamma, 1.0f) ? Hint::IsIdentity : Hint::NoHint));
}
static QColorTransferFunction fromSRgb()
{
- return QColorTransferFunction(1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f, 2.4f);
+ return QColorTransferFunction(1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f, 2.4f,
+ Hints(Hint::Calculated) | Hint::IsSRgb);
}
static QColorTransferFunction fromProPhotoRgb()
{
- return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f);
+ return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f,
+ Hints(Hint::Calculated));
}
bool matches(const QColorTransferFunction &o) const
{
@@ -116,7 +132,20 @@ public:
float m_f;
float m_g;
+ enum class Hint : quint32 {
+ NoHint = 0,
+ Calculated = 1,
+ IsGamma = 2,
+ IsIdentity = 4,
+ IsSRgb = 8
+ };
+
+ Q_DECLARE_FLAGS(Hints, Hint);
+
private:
+ QColorTransferFunction(float a, float b, float c, float d, float e, float f, float g, Hints flags) noexcept
+ : m_a(a), m_b(b), m_c(c), m_d(d), m_e(e), m_f(f), m_g(g), m_flags(flags)
+ { }
static inline bool paramCompare(float p1, float p2)
{
// Much fuzzier than fuzzy compare.
@@ -127,7 +156,7 @@ private:
void updateHints() const
{
- if (m_flags & quint32(Hints::Calculated))
+ if (m_flags & Hint::Calculated)
return;
// We do not consider the case with m_d = 1.0f linear or simple,
// since it wouldn't be linear for applyExtended().
@@ -135,24 +164,21 @@ private:
&& paramCompare(m_d, 0.0f)
&& paramCompare(m_e, 0.0f);
if (simple) {
- m_flags |= quint32(Hints::IsGamma);
+ m_flags |= Hint::IsGamma;
if (qFuzzyCompare(m_g, 1.0f))
- m_flags |= quint32(Hints::IsLinear);
+ m_flags |= Hint::IsIdentity;
} else {
if (*this == fromSRgb())
- m_flags |= quint32(Hints::IsSRgb);
+ m_flags |= Hint::IsSRgb;
}
- m_flags |= quint32(Hints::Calculated);
+ m_flags |= Hint::Calculated;
}
- enum class Hints : quint32 {
- Calculated = 1,
- IsGamma = 2,
- IsLinear = 4,
- IsSRgb = 8
- };
- mutable quint32 m_flags;
+
+ mutable Hints m_flags;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(QColorTransferFunction::Hints);
+
inline bool operator==(const QColorTransferFunction &f1, const QColorTransferFunction &f2)
{
return f1.matches(f2);
diff --git a/src/gui/painting/qcolortransfertable_p.h b/src/gui/painting/qcolortransfertable_p.h
index dc0f5804fd..ce6ad0c4b2 100644
--- a/src/gui/painting/qcolortransfertable_p.h
+++ b/src/gui/painting/qcolortransfertable_p.h
@@ -29,25 +29,38 @@ QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QColorTransferTable
{
public:
- QColorTransferTable() noexcept
- : m_tableSize(0)
- { }
- QColorTransferTable(uint32_t size, const QList<uint8_t> &table) noexcept
- : m_tableSize(size), m_table8(table)
+ enum Type : uint8_t {
+ TwoWay = 0,
+ OneWay,
+ };
+ QColorTransferTable() noexcept = default;
+ QColorTransferTable(uint32_t size, const QList<uint8_t> &table, Type type = TwoWay) noexcept
+ : m_type(type), m_tableSize(size), m_table8(table)
{
Q_ASSERT(qsizetype(size) <= table.size());
}
- QColorTransferTable(uint32_t size, const QList<uint16_t> &table) noexcept
- : m_tableSize(size), m_table16(table)
+ QColorTransferTable(uint32_t size, const QList<uint16_t> &table, Type type = TwoWay) noexcept
+ : m_type(type), m_tableSize(size), m_table16(table)
{
Q_ASSERT(qsizetype(size) <= table.size());
}
- bool isEmpty() const
+ bool isEmpty() const noexcept
{
return m_tableSize == 0;
}
+ bool isIdentity() const
+ {
+ if (isEmpty())
+ return true;
+ if (m_tableSize != 2)
+ return false;
+ if (!m_table8.isEmpty())
+ return m_table8[0] == 0 && m_table8[1] == 255;
+ return m_table16[0] == 0 && m_table16[1] == 65535;
+ }
+
bool checkValidity() const
{
if (isEmpty())
@@ -58,7 +71,11 @@ public:
// At least 2 elements
if (m_tableSize < 2)
return false;
- // The table must describe an injective curve:
+ return (m_type == OneWay) || checkInvertibility();
+ }
+ bool checkInvertibility() const
+ {
+ // The two-way tables must describe an injective curve:
if (!m_table8.isEmpty()) {
uint8_t val = 0;
for (uint i = 0; i < m_tableSize; ++i) {
@@ -80,15 +97,17 @@ public:
float apply(float x) const
{
+ if (isEmpty())
+ return x;
x = std::clamp(x, 0.0f, 1.0f);
x *= m_tableSize - 1;
- const uint32_t lo = static_cast<uint32_t>(std::floor(x));
+ const uint32_t lo = static_cast<uint32_t>(x);
const uint32_t hi = std::min(lo + 1, m_tableSize - 1);
const float frac = x - lo;
if (!m_table16.isEmpty())
- return (m_table16[lo] * (1.0f - frac) + m_table16[hi] * frac) * (1.0f/65535.0f);
+ return (m_table16[lo] + (m_table16[hi] - m_table16[lo]) * frac) * (1.0f/65535.0f);
if (!m_table8.isEmpty())
- return (m_table8[lo] * (1.0f - frac) + m_table8[hi] * frac) * (1.0f/255.0f);
+ return (m_table8[lo] + (m_table8[hi] - m_table8[lo]) * frac) * (1.0f/255.0f);
return x;
}
@@ -96,47 +115,25 @@ public:
float applyInverse(float x, float resultLargerThan = 0.0f) const
{
Q_ASSERT(resultLargerThan >= 0.0f && resultLargerThan <= 1.0f);
+ Q_ASSERT(m_type == TwoWay);
if (x <= 0.0f)
return 0.0f;
if (x >= 1.0f)
return 1.0f;
- if (!m_table16.isEmpty()) {
- const float v = x * 65535.0f;
- uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1)));
- auto it = std::lower_bound(m_table16.cbegin() + i, m_table16.cend(), v);
- i = it - m_table16.cbegin();
- if (i == 0)
- return 0.0f;
- if (i >= m_tableSize - 1)
- return 1.0f;
- const float y1 = m_table16[i - 1];
- const float y2 = m_table16[i];
- Q_ASSERT(v >= y1 && v <= y2);
- const float fr = (v - y1) / (y2 - y1);
- return (i + fr) * (1.0f / (m_tableSize - 1));
-
- }
- if (!m_table8.isEmpty()) {
- const float v = x * 255.0f;
- uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1)));
- auto it = std::lower_bound(m_table8.cbegin() + i, m_table8.cend(), v);
- i = it - m_table8.cbegin();
- if (i == 0)
- return 0.0f;
- if (i >= m_tableSize - 1)
- return 1.0f;
- const float y1 = m_table8[i - 1];
- const float y2 = m_table8[i];
- Q_ASSERT(v >= y1 && v <= y2);
- const float fr = (v - y1) / (y2 - y1);
- return (i + fr) * (1.0f / (m_tableSize - 1));
- }
+ if (!m_table16.isEmpty())
+ return inverseLookup(x * 65535.0f, resultLargerThan, m_table16, m_tableSize - 1);
+ if (!m_table8.isEmpty())
+ return inverseLookup(x * 255.0f, resultLargerThan, m_table8, m_tableSize - 1);
return x;
}
bool asColorTransferFunction(QColorTransferFunction *transferFn)
{
Q_ASSERT(transferFn);
+ if (isEmpty()) {
+ *transferFn = QColorTransferFunction();
+ return true;
+ }
if (m_tableSize < 2)
return false;
if (!m_table8.isEmpty() && (m_table8[0] != 0 || m_table8[m_tableSize - 1] != 255))
@@ -186,15 +183,36 @@ public:
friend inline bool operator!=(const QColorTransferTable &t1, const QColorTransferTable &t2);
friend inline bool operator==(const QColorTransferTable &t1, const QColorTransferTable &t2);
- uint32_t m_tableSize;
+ Type m_type = TwoWay;
+ uint32_t m_tableSize = 0;
QList<uint8_t> m_table8;
QList<uint16_t> m_table16;
+private:
+ template<typename T>
+ static float inverseLookup(float needle, float resultLargerThan, const QList<T> &table, quint32 tableMax)
+ {
+ uint32_t i = static_cast<uint32_t>(resultLargerThan * tableMax);
+ auto it = std::lower_bound(table.cbegin() + i, table.cend(), needle);
+ i = it - table.cbegin();
+ if (i == 0)
+ return 0.0f;
+ if (i >= tableMax)
+ return 1.0f;
+ const float y1 = table[i - 1];
+ const float y2 = table[i];
+ Q_ASSERT(needle >= y1 && needle <= y2);
+ const float fr = (needle - y1) / (y2 - y1);
+ return (i + fr) * (1.0f / tableMax);
+ }
+
};
inline bool operator!=(const QColorTransferTable &t1, const QColorTransferTable &t2)
{
if (t1.m_tableSize != t2.m_tableSize)
return true;
+ if (t1.m_type != t2.m_type)
+ return true;
if (t1.m_table8.isEmpty() != t2.m_table8.isEmpty())
return true;
if (t1.m_table16.isEmpty() != t2.m_table16.isEmpty())
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp
index 579d99d09c..33b1dcdeb0 100644
--- a/src/gui/painting/qcolortransform.cpp
+++ b/src/gui/painting/qcolortransform.cpp
@@ -1,9 +1,11 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qcolortransform.h"
#include "qcolortransform_p.h"
+#include "qcmyk_p.h"
+#include "qcolorclut_p.h"
#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
@@ -139,11 +141,31 @@ bool QColorTransform::compare(const QColorTransform &other) const
return false;
if (bool(d->colorSpaceOut) != bool(other.d->colorSpaceOut))
return false;
- for (int i = 0; i < 3; ++i) {
- if (d->colorSpaceIn && d->colorSpaceIn->trc[i] != other.d->colorSpaceIn->trc[i])
+ if (d->colorSpaceIn) {
+ if (d->colorSpaceIn->transformModel != other.d->colorSpaceIn->transformModel)
return false;
- if (d->colorSpaceOut && d->colorSpaceOut->trc[i] != other.d->colorSpaceOut->trc[i])
+ if (d->colorSpaceIn->isThreeComponentMatrix()) {
+ for (int i = 0; i < 3; ++i) {
+ if (d->colorSpaceIn && d->colorSpaceIn->trc[i] != other.d->colorSpaceIn->trc[i])
+ return false;
+ }
+ } else {
+ if (!d->colorSpaceIn->equals(other.d->colorSpaceIn.constData()))
+ return false;
+ }
+ }
+ if (d->colorSpaceOut) {
+ if (d->colorSpaceOut->transformModel != other.d->colorSpaceOut->transformModel)
return false;
+ if (d->colorSpaceOut->isThreeComponentMatrix()) {
+ for (int i = 0; i < 3; ++i) {
+ if (d->colorSpaceOut && d->colorSpaceOut->trc[i] != other.d->colorSpaceOut->trc[i])
+ return false;
+ }
+ } else {
+ if (!d->colorSpaceOut->equals(other.d->colorSpaceOut.constData()))
+ return false;
+ }
}
return true;
}
@@ -159,29 +181,7 @@ QRgb QColorTransform::map(QRgb argb) const
return argb;
constexpr float f = 1.0f / 255.0f;
QColorVector c = { qRed(argb) * f, qGreen(argb) * f, qBlue(argb) * f };
- if (d->colorSpaceIn->lut.generated.loadAcquire()) {
- c.x = d->colorSpaceIn->lut[0]->toLinear(c.x);
- c.y = d->colorSpaceIn->lut[1]->toLinear(c.y);
- c.z = d->colorSpaceIn->lut[2]->toLinear(c.z);
- } else {
- c.x = d->colorSpaceIn->trc[0].apply(c.x);
- c.y = d->colorSpaceIn->trc[1].apply(c.y);
- c.z = d->colorSpaceIn->trc[2].apply(c.z);
- }
- c = d->colorMatrix.map(c);
- c.x = std::max(0.0f, std::min(1.0f, c.x));
- c.y = std::max(0.0f, std::min(1.0f, c.y));
- c.z = std::max(0.0f, std::min(1.0f, c.z));
- if (d->colorSpaceOut->lut.generated.loadAcquire()) {
- c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
- c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
- c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
- } else {
- c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
- c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
- c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
- }
-
+ c = d->map(c);
return qRgba(c.x * 255 + 0.5f, c.y * 255 + 0.5f, c.z * 255 + 0.5f, qAlpha(argb));
}
@@ -196,29 +196,7 @@ QRgba64 QColorTransform::map(QRgba64 rgba64) const
return rgba64;
constexpr float f = 1.0f / 65535.0f;
QColorVector c = { rgba64.red() * f, rgba64.green() * f, rgba64.blue() * f };
- if (d->colorSpaceIn->lut.generated.loadAcquire()) {
- c.x = d->colorSpaceIn->lut[0]->toLinear(c.x);
- c.y = d->colorSpaceIn->lut[1]->toLinear(c.y);
- c.z = d->colorSpaceIn->lut[2]->toLinear(c.z);
- } else {
- c.x = d->colorSpaceIn->trc[0].apply(c.x);
- c.y = d->colorSpaceIn->trc[1].apply(c.y);
- c.z = d->colorSpaceIn->trc[2].apply(c.z);
- }
- c = d->colorMatrix.map(c);
- c.x = std::max(0.0f, std::min(1.0f, c.x));
- c.y = std::max(0.0f, std::min(1.0f, c.y));
- c.z = std::max(0.0f, std::min(1.0f, c.z));
- if (d->colorSpaceOut->lut.generated.loadAcquire()) {
- c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
- c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
- c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
- } else {
- c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
- c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
- c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
- }
-
+ c = d->map(c);
return QRgba64::fromRgba64(c.x * 65535.f + 0.5f, c.y * 65535.f + 0.5f, c.z * 65535.f + 0.5f, rgba64.alpha());
}
@@ -232,14 +210,11 @@ QRgbaFloat16 QColorTransform::map(QRgbaFloat16 rgbafp16) const
{
if (!d)
return rgbafp16;
- QColorVector c;
- c.x = d->colorSpaceIn->trc[0].applyExtended(rgbafp16.r);
- c.y = d->colorSpaceIn->trc[1].applyExtended(rgbafp16.g);
- c.z = d->colorSpaceIn->trc[2].applyExtended(rgbafp16.b);
- c = d->colorMatrix.map(c);
- rgbafp16.r = qfloat16(d->colorSpaceOut->trc[0].applyInverseExtended(c.x));
- rgbafp16.g = qfloat16(d->colorSpaceOut->trc[1].applyInverseExtended(c.y));
- rgbafp16.b = qfloat16(d->colorSpaceOut->trc[2].applyInverseExtended(c.z));
+ QColorVector c(rgbafp16.r, rgbafp16.g, rgbafp16.b);
+ c = d->mapExtended(c);
+ rgbafp16.r = qfloat16(c.x);
+ rgbafp16.g = qfloat16(c.y);
+ rgbafp16.b = qfloat16(c.z);
return rgbafp16;
}
@@ -253,14 +228,11 @@ QRgbaFloat32 QColorTransform::map(QRgbaFloat32 rgbafp32) const
{
if (!d)
return rgbafp32;
- QColorVector c;
- c.x = d->colorSpaceIn->trc[0].applyExtended(rgbafp32.r);
- c.y = d->colorSpaceIn->trc[1].applyExtended(rgbafp32.g);
- c.z = d->colorSpaceIn->trc[2].applyExtended(rgbafp32.b);
- c = d->colorMatrix.map(c);
- rgbafp32.r = d->colorSpaceOut->trc[0].applyInverseExtended(c.x);
- rgbafp32.g = d->colorSpaceOut->trc[1].applyInverseExtended(c.y);
- rgbafp32.b = d->colorSpaceOut->trc[2].applyInverseExtended(c.z);
+ QColorVector c(rgbafp32.r, rgbafp32.g, rgbafp32.b);
+ c = d->mapExtended(c);
+ rgbafp32.r = c.x;
+ rgbafp32.g = c.y;
+ rgbafp32.b = c.z;
return rgbafp32;
}
@@ -273,44 +245,42 @@ QColor QColorTransform::map(const QColor &color) const
if (!d)
return color;
QColor clr = color;
- if (color.spec() != QColor::ExtendedRgb || color.spec() != QColor::Rgb)
- clr = clr.toRgb();
-
- QColorVector c = { (float)clr.redF(), (float)clr.greenF(), (float)clr.blueF() };
- if (clr.spec() == QColor::ExtendedRgb) {
- c.x = d->colorSpaceIn->trc[0].applyExtended(c.x);
- c.y = d->colorSpaceIn->trc[1].applyExtended(c.y);
- c.z = d->colorSpaceIn->trc[2].applyExtended(c.z);
- } else {
- c.x = d->colorSpaceIn->trc[0].apply(c.x);
- c.y = d->colorSpaceIn->trc[1].apply(c.y);
- c.z = d->colorSpaceIn->trc[2].apply(c.z);
- }
- c = d->colorMatrix.map(c);
- bool inGamut = c.x >= 0.0f && c.x <= 1.0f && c.y >= 0.0f && c.y <= 1.0f && c.z >= 0.0f && c.z <= 1.0f;
- if (inGamut) {
- if (d->colorSpaceOut->lut.generated.loadAcquire()) {
- c.x = d->colorSpaceOut->lut[0]->fromLinear(c.x);
- c.y = d->colorSpaceOut->lut[1]->fromLinear(c.y);
- c.z = d->colorSpaceOut->lut[2]->fromLinear(c.z);
- } else {
- c.x = d->colorSpaceOut->trc[0].applyInverse(c.x);
- c.y = d->colorSpaceOut->trc[1].applyInverse(c.y);
- c.z = d->colorSpaceOut->trc[2].applyInverse(c.z);
- }
- } else {
- c.x = d->colorSpaceOut->trc[0].applyInverseExtended(c.x);
- c.y = d->colorSpaceOut->trc[1].applyInverseExtended(c.y);
- c.z = d->colorSpaceOut->trc[2].applyInverseExtended(c.z);
+ if (d->colorSpaceIn->colorModel == QColorSpace::ColorModel::Rgb) {
+ if (color.spec() != QColor::ExtendedRgb && color.spec() != QColor::Rgb)
+ clr = clr.toRgb();
+ } else if (d->colorSpaceIn->colorModel == QColorSpace::ColorModel::Cmyk) {
+ if (color.spec() != QColor::Cmyk)
+ clr = clr.toCmyk();
}
+
+ QColorVector c =
+ (clr.spec() == QColor::Cmyk)
+ ? QColorVector(clr.cyanF(), clr.magentaF(), clr.yellowF(), clr.blackF())
+ : QColorVector(clr.redF(), clr.greenF(), clr.blueF());
+
+ c = d->mapExtended(c);
+
QColor out;
- out.setRgbF(c.x, c.y, c.z, color.alphaF());
+ if (d->colorSpaceOut->colorModel == QColorSpace::ColorModel::Cmyk) {
+ c.x = std::clamp(c.x, 0.f, 1.f);
+ c.y = std::clamp(c.y, 0.f, 1.f);
+ c.z = std::clamp(c.z, 0.f, 1.f);
+ c.w = std::clamp(c.w, 0.f, 1.f);
+ out.setCmykF(c.x, c.y, c.z, c.w, color.alphaF());
+ } else {
+ out.setRgbF(c.x, c.y, c.z, color.alphaF());
+ }
return out;
}
// Optimized sub-routines for fast block based conversion:
-template<bool DoClamp = true>
+enum ApplyMatrixForm {
+ DoNotClamp = 0,
+ DoClamp = 1
+};
+
+template<ApplyMatrixForm doClamp = DoClamp>
static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorMatrix &colorMatrix)
{
#if defined(__SSE2__)
@@ -330,7 +300,7 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM
cx = _mm_add_ps(cx, cy);
cx = _mm_add_ps(cx, cz);
// Clamp:
- if (DoClamp) {
+ if (doClamp) {
cx = _mm_min_ps(cx, maxV);
cx = _mm_max_ps(cx, minV);
}
@@ -350,19 +320,19 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM
cx = vaddq_f32(cx, cy);
cx = vaddq_f32(cx, cz);
// Clamp:
- if (DoClamp) {
+ if (doClamp) {
cx = vminq_f32(cx, maxV);
cx = vmaxq_f32(cx, minV);
}
vst1q_f32(&buffer[j].x, cx);
}
#else
- for (int j = 0; j < len; ++j) {
+ for (qsizetype j = 0; j < len; ++j) {
const QColorVector cv = colorMatrix.map(buffer[j]);
- if (DoClamp) {
- buffer[j].x = std::max(0.0f, std::min(1.0f, cv.x));
- buffer[j].y = std::max(0.0f, std::min(1.0f, cv.y));
- buffer[j].z = std::max(0.0f, std::min(1.0f, cv.z));
+ if (doClamp) {
+ buffer[j].x = std::clamp(cv.x, 0.f, 1.f);
+ buffer[j].y = std::clamp(cv.y, 0.f, 1.f);
+ buffer[j].z = std::clamp(cv.z, 0.f, 1.f);
} else {
buffer[j] = cv;
}
@@ -370,6 +340,39 @@ static void applyMatrix(QColorVector *buffer, const qsizetype len, const QColorM
#endif
}
+template<ApplyMatrixForm doClamp = DoClamp>
+static void clampIfNeeded(QColorVector *buffer, const qsizetype len)
+{
+ if constexpr (doClamp != DoClamp)
+ return;
+#if defined(__SSE2__)
+ const __m128 minV = _mm_set1_ps(0.0f);
+ const __m128 maxV = _mm_set1_ps(1.0f);
+ for (qsizetype j = 0; j < len; ++j) {
+ __m128 c = _mm_loadu_ps(&buffer[j].x);
+ c = _mm_min_ps(c, maxV);
+ c = _mm_max_ps(c, minV);
+ _mm_storeu_ps(&buffer[j].x, c);
+ }
+#elif defined(__ARM_NEON__)
+ const float32x4_t minV = vdupq_n_f32(0.0f);
+ const float32x4_t maxV = vdupq_n_f32(1.0f);
+ for (qsizetype j = 0; j < len; ++j) {
+ float32x4_t c = vld1q_f32(&buffer[j].x);
+ c = vminq_f32(c, maxV);
+ c = vmaxq_f32(c, minV);
+ vst1q_f32(&buffer[j].x, c);
+ }
+#else
+ for (qsizetype j = 0; j < len; ++j) {
+ const QColorVector cv = buffer[j];
+ buffer[j].x = std::clamp(cv.x, 0.f, 1.f);
+ buffer[j].y = std::clamp(cv.y, 0.f, 1.f);
+ buffer[j].z = std::clamp(cv.z, 0.f, 1.f);
+ }
+#endif
+}
+
#if defined(__SSE2__) || defined(__ARM_NEON__)
template<typename T>
static constexpr inline bool isArgb();
@@ -386,9 +389,29 @@ inline int getAlpha<QRgb>(const QRgb &p)
template<>
inline int getAlpha<QRgba64>(const QRgba64 &p)
{ return p.alpha(); }
+
#endif
template<typename T>
+static float getAlphaF(const T &);
+template<> float getAlphaF(const QRgb &r)
+{
+ return qAlpha(r) * (1.f / 255.f);
+}
+template<> float getAlphaF(const QCmyk32 &)
+{
+ return 1.f;
+}
+template<> float getAlphaF(const QRgba64 &r)
+{
+ return r.alpha() * (1.f / 65535.f);
+}
+template<> float getAlphaF(const QRgbaFloat32 &r)
+{
+ return r.a;
+}
+
+template<typename T>
static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr);
template<typename T>
static void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr);
@@ -424,7 +447,7 @@ inline void loadP<QRgba64>(const QRgba64 &p, __m128i &v)
template<typename T>
static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
constexpr bool isARGB = isArgb<T>();
for (qsizetype i = 0; i < len; ++i) {
@@ -443,7 +466,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp
vf = _mm_andnot_ps(vAlphaMask, vf);
// LUT
- v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = isARGB ? _mm_extract_epi16(v, 4) : _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = isARGB ? _mm_extract_epi16(v, 0) : _mm_extract_epi16(v, 4);
@@ -459,7 +482,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp
template<>
void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
const __m128 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
@@ -481,7 +504,7 @@ void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *s
const __m128 over = _mm_cmpgt_ps(vf, vOne);
if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) {
// Within gamut
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -500,7 +523,7 @@ void loadPremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *s
}
}
-// Load to [0-4080] in 4x32 SIMD
+// Load to [0->TrcResolution] in 4x32 SIMD
template<typename T>
static inline void loadPU(const T &p, __m128i &v);
@@ -514,7 +537,7 @@ inline void loadPU<QRgb>(const QRgb &p, __m128i &v)
v = _mm_unpacklo_epi8(v, _mm_setzero_si128());
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
- v = _mm_slli_epi32(v, 4);
+ v = _mm_slli_epi32(v, QColorTrcLut::ShiftUp);
}
template<>
@@ -527,7 +550,7 @@ inline void loadPU<QRgba64>(const QRgba64 &p, __m128i &v)
#else
v = _mm_unpacklo_epi16(v, _mm_setzero_si128());
#endif
- v = _mm_srli_epi32(v, 4);
+ v = _mm_srli_epi32(v, QColorTrcLut::ShiftDown);
}
template<typename T>
@@ -552,7 +575,7 @@ void loadUnpremultiplied(QColorVector *buffer, const T *src, const qsizetype len
template<>
void loadUnpremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
const __m128 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
@@ -562,7 +585,7 @@ void loadUnpremultiplied<QRgbaFloat32>(QColorVector *buffer, const QRgbaFloat32
const __m128 over = _mm_cmpgt_ps(vf, vOne);
if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) {
// Within gamut
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -623,7 +646,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp
vf = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(vf), vAlphaMask));
// LUT
- v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f)));
+ v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f)));
const int ridx = isARGB ? vgetq_lane_u32(v, 2) : vgetq_lane_u32(v, 0);
const int gidx = vgetq_lane_u32(v, 1);
const int bidx = isARGB ? vgetq_lane_u32(v, 0) : vgetq_lane_u32(v, 2);
@@ -636,7 +659,7 @@ static void loadPremultiplied(QColorVector *buffer, const T *src, const qsizetyp
}
}
-// Load to [0-4080] in 4x32 SIMD
+// Load to [0->TrcResultion] in 4x32 SIMD
template<typename T>
static inline void loadPU(const T &p, uint32x4_t &v);
@@ -644,7 +667,7 @@ template<>
inline void loadPU<QRgb>(const QRgb &p, uint32x4_t &v)
{
v = vmovl_u16(vget_low_u16(vmovl_u8(vreinterpret_u8_u32(vmov_n_u32(p)))));
- v = vshlq_n_u32(v, 4);
+ v = vshlq_n_u32(v, QColorTrcLut::ShiftUp);
}
template<>
@@ -653,7 +676,7 @@ inline void loadPU<QRgba64>(const QRgba64 &p, uint32x4_t &v)
uint16x4_t v16 = vreinterpret_u16_u64(vld1_u64(reinterpret_cast<const uint64_t *>(&p)));
v16 = vsub_u16(v16, vshr_n_u16(v16, 8));
v = vmovl_u16(v16);
- v = vshrq_n_u32(v, 4);
+ v = vshrq_n_u32(v, QColorTrcLut::ShiftDown);
}
template<typename T>
@@ -682,7 +705,7 @@ void loadPremultiplied<QRgb>(QColorVector *buffer, const QRgb *src, const qsizet
const uint p = src[i];
const int a = qAlpha(p);
if (a) {
- const float ia = 4080.0f / a;
+ const float ia = float(QColorTrcLut::Resolution) / a;
const int ridx = int(qRed(p) * ia + 0.5f);
const int gidx = int(qGreen(p) * ia + 0.5f);
const int bidx = int(qBlue(p) * ia + 0.5f);
@@ -702,7 +725,7 @@ void loadPremultiplied<QRgba64>(QColorVector *buffer, const QRgba64 *src, const
const QRgba64 &p = src[i];
const int a = p.alpha();
if (a) {
- const float ia = 4080.0f / a;
+ const float ia = float(QColorTrcLut::Resolution) / a;
const int ridx = int(p.red() * ia + 0.5f);
const int gidx = int(p.green() * ia + 0.5f);
const int bidx = int(p.blue() * ia + 0.5f);
@@ -792,17 +815,18 @@ inline void storeP<QRgba64>(QRgba64 &p, __m128i &v, int a)
#endif
}
-template<typename T>
-static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+template<typename D, typename S,
+ typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
+static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
- constexpr bool isARGB = isArgb<T>();
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
__m128 va = _mm_mul_ps(_mm_set1_ps(a), iFF00);
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
@@ -813,21 +837,21 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer,
vf = _mm_cvtepi32_ps(v);
vf = _mm_mul_ps(vf, va);
v = _mm_cvtps_epi32(vf);
- storeP<T>(dst[i], v, a);
+ storeP<D>(dst[i], v, a);
}
}
-template<>
-void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
- const QColorVector *buffer, const qsizetype len,
- const QColorTransformPrivate *d_ptr)
+template<typename S>
+static void storePremultiplied(QRgbaFloat32 *dst, const S *src,
+ const QColorVector *buffer, const qsizetype len,
+ const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
for (qsizetype i = 0; i < len; ++i) {
- const float a = src[i].a;
+ const float a = getAlphaF<S>(src[i]);
__m128 va = _mm_set1_ps(a);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
const __m128 under = _mm_cmplt_ps(vf, vZero);
@@ -835,7 +859,7 @@ void storePremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src
if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) {
// Within gamut
va = _mm_mul_ps(va, viFF00);
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -874,16 +898,17 @@ inline void storePU<QRgba64>(QRgba64 &p, __m128i &v, int a)
_mm_storel_epi64((__m128i *)&p, v);
}
-template<typename T>
-static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+template<typename D, typename S,
+ typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
+static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
- constexpr bool isARGB = isArgb<T>();
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -891,27 +916,27 @@ static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffe
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], isARGB ? 2 : 0);
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], 1);
v = _mm_insert_epi16(v, d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], isARGB ? 0 : 2);
- storePU<T>(dst[i], v, a);
+ storePU<D>(dst[i], v, a);
}
}
-template<>
-void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
- const QColorVector *buffer, const qsizetype len,
- const QColorTransformPrivate *d_ptr)
+template<typename S>
+void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src,
+ const QColorVector *buffer, const qsizetype len,
+ const QColorTransformPrivate *d_ptr)
{
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
for (qsizetype i = 0; i < len; ++i) {
- const float a = src[i].a;
+ const float a = getAlphaF<S>(src[i]);
__m128 vf = _mm_loadu_ps(&buffer[i].x);
const __m128 under = _mm_cmplt_ps(vf, vZero);
const __m128 over = _mm_cmpgt_ps(vf, vOne);
if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) {
// Within gamut
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -931,15 +956,14 @@ void storeUnpremultiplied<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *s
}
template<typename T>
-static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
constexpr bool isARGB = isArgb<T>();
for (qsizetype i = 0; i < len; ++i) {
__m128 vf = _mm_loadu_ps(&buffer[i].x);
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -952,12 +976,10 @@ static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const
}
template<>
-void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
- const QColorVector *buffer, const qsizetype len,
- const QColorTransformPrivate *d_ptr)
+void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
+ const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
- const __m128 v4080 = _mm_set1_ps(4080.f);
+ const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 vZero = _mm_set1_ps(0.0f);
const __m128 vOne = _mm_set1_ps(1.0f);
const __m128 viFF00 = _mm_set1_ps(1.0f / (255 * 256));
@@ -967,7 +989,7 @@ void storeOpaque<QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src,
const __m128 over = _mm_cmpgt_ps(vf, vOne);
if (_mm_movemask_ps(_mm_or_ps(under, over)) == 0) {
// Within gamut
- __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, v4080));
+ __m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
const int gidx = _mm_extract_epi16(v, 2);
const int bidx = _mm_extract_epi16(v, 4);
@@ -1000,16 +1022,17 @@ inline void storeP<QRgba64>(QRgba64 &p, const uint16x4_t &v)
vst1_u16((uint16_t *)&p, v);
}
-template<typename T>
-static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+template<typename D, typename S,
+ typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
+static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
const float iFF00 = 1.0f / (255 * 256);
- constexpr bool isARGB = isArgb<T>();
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
float32x4_t vf = vld1q_f32(&buffer[i].x);
- uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f)));
+ uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f)));
const int ridx = vgetq_lane_u32(v, 0);
const int gidx = vgetq_lane_u32(v, 1);
const int bidx = vgetq_lane_u32(v, 2);
@@ -1022,7 +1045,7 @@ static void storePremultiplied(T *dst, const T *src, const QColorVector *buffer,
v = vcvtq_u32_f32(vf);
uint16x4_t v16 = vmovn_u32(v);
v16 = vset_lane_u16(a, v16, 3);
- storeP<T>(dst[i], v16);
+ storeP<D>(dst[i], v16);
}
}
@@ -1044,34 +1067,34 @@ inline void storePU<QRgba64>(QRgba64 &p, uint16x4_t &v, int a)
vst1_u16((uint16_t *)&p, v);
}
-template<typename T>
-static void storeUnpremultiplied(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+template<typename D, typename S,
+ typename = std::enable_if_t<!std::is_same_v<D, QRgbaFloat32>, void>>
+static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- constexpr bool isARGB = isArgb<T>();
+ constexpr bool isARGB = isArgb<D>();
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<T>(src[i]);
+ const int a = getAlpha<S>(src[i]);
float32x4_t vf = vld1q_f32(&buffer[i].x);
- uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f))));
+ uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f))));
const int ridx = vget_lane_u16(v, 0);
const int gidx = vget_lane_u16(v, 1);
const int bidx = vget_lane_u16(v, 2);
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[0]->m_fromLinear[ridx], v, isARGB ? 2 : 0);
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[1]->m_fromLinear[gidx], v, 1);
v = vset_lane_u16(d_ptr->colorSpaceOut->lut[2]->m_fromLinear[bidx], v, isARGB ? 0 : 2);
- storePU<T>(dst[i], v, a);
+ storePU<D>(dst[i], v, a);
}
}
template<typename T>
-static void storeOpaque(T *dst, const T *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(T *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
constexpr bool isARGB = isArgb<T>();
for (qsizetype i = 0; i < len; ++i) {
float32x4_t vf = vld1q_f32(&buffer[i].x);
- uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, 4080.f), vdupq_n_f32(0.5f))));
+ uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f))));
const int ridx = vget_lane_u16(v, 0);
const int gidx = vget_lane_u16(v, 1);
const int bidx = vget_lane_u16(v, 2);
@@ -1088,9 +1111,9 @@ static void storePremultiplied(QRgb *dst, const QRgb *src, const QColorVector *b
for (qsizetype i = 0; i < len; ++i) {
const int a = qAlpha(src[i]);
const float fa = a / (255.0f * 256.0f);
- const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * 4080.0f + 0.5f)];
- const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * 4080.0f + 0.5f)];
- const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)];
+ const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * float(QColorTrcLut::Resolution) + 0.5f)];
+ const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * float(QColorTrcLut::Resolution) + 0.5f)];
+ const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * float(QColorTrcLut::Resolution) + 0.5f)];
dst[i] = qRgba(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a);
}
}
@@ -1106,10 +1129,9 @@ static void storeUnpremultiplied(QRgb *dst, const QRgb *src, const QColorVector
}
}
-static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(QRgb *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
for (qsizetype i = 0; i < len; ++i) {
const int r = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].x);
const int g = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y);
@@ -1118,34 +1140,36 @@ static void storeOpaque(QRgb *dst, const QRgb *src, const QColorVector *buffer,
}
}
-static void storePremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
+template<typename S>
+static void storePremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
- const int a = src[i].alpha();
+ const int a = getAlphaF(src[i]) * 65535.f;
const float fa = a / (255.0f * 256.0f);
- const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * 4080.0f + 0.5f)];
- const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * 4080.0f + 0.5f)];
- const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * 4080.0f + 0.5f)];
+ const float r = d_ptr->colorSpaceOut->lut[0]->m_fromLinear[int(buffer[i].x * float(QColorTrcLut::Resolution) + 0.5f)];
+ const float g = d_ptr->colorSpaceOut->lut[1]->m_fromLinear[int(buffer[i].y * float(QColorTrcLut::Resolution) + 0.5f)];
+ const float b = d_ptr->colorSpaceOut->lut[2]->m_fromLinear[int(buffer[i].z * float(QColorTrcLut::Resolution) + 0.5f)];
dst[i] = qRgba64(r * fa + 0.5f, g * fa + 0.5f, b * fa + 0.5f, a);
}
}
-static void storeUnpremultiplied(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
+template<typename S>
+static void storeUnpremultiplied(QRgba64 *dst, const S *src, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
+ const int a = getAlphaF(src[i]) * 65535.f;
const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x);
const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
const int b = d_ptr->colorSpaceOut->lut[2]->u16FromLinearF32(buffer[i].z);
- dst[i] = qRgba64(r, g, b, src[i].alpha());
+ dst[i] = qRgba64(r, g, b, a);
}
}
-static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(QRgba64 *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
for (qsizetype i = 0; i < len; ++i) {
const int r = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].x);
const int g = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
@@ -1155,11 +1179,12 @@ static void storeOpaque(QRgba64 *dst, const QRgba64 *src, const QColorVector *bu
}
#endif
#if !defined(__SSE2__)
-static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer,
+template<typename S>
+static void storePremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer,
const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
- const float a = src[i].a;
+ const float a = getAlphaF(src[i]);
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x) * a;
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y) * a;
dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z) * a;
@@ -1167,11 +1192,12 @@ static void storePremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const
}
}
-static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer,
+template<typename S>
+static void storeUnpremultiplied(QRgbaFloat32 *dst, const S *src, const QColorVector *buffer,
const qsizetype len, const QColorTransformPrivate *d_ptr)
{
for (qsizetype i = 0; i < len; ++i) {
- const float a = src[i].a;
+ const float a = getAlphaF(src[i]);
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x);
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y);
dst[i].b = d_ptr->colorSpaceOut->trc[2].applyInverseExtended(buffer[i].z);
@@ -1179,10 +1205,9 @@ static void storeUnpremultiplied(QRgbaFloat32 *dst, const QRgbaFloat32 *src, con
}
}
-static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
for (qsizetype i = 0; i < len; ++i) {
dst[i].r = d_ptr->colorSpaceOut->trc[0].applyInverseExtended(buffer[i].x);
dst[i].g = d_ptr->colorSpaceOut->trc[1].applyInverseExtended(buffer[i].y);
@@ -1191,20 +1216,35 @@ static void storeOpaque(QRgbaFloat32 *dst, const QRgbaFloat32 *src, const QColor
}
}
#endif
-static void storeGray(quint8 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len,
+
+static void loadGray(QColorVector *buffer, const quint8 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ }
+}
+
+static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ }
+}
+
+static void storeOpaque(quint8 *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
for (qsizetype i = 0; i < len; ++i)
- dst[i] = d_ptr->colorSpaceOut->lut[1]->u8FromLinearF32(buffer[i].y);
+ dst[i] = d_ptr->colorSpaceOut->lut[0]->u8FromLinearF32(buffer[i].y);
}
-static void storeGray(quint16 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len,
+static void storeOpaque(quint16 *dst, const QColorVector *buffer, const qsizetype len,
const QColorTransformPrivate *d_ptr)
{
- Q_UNUSED(src);
for (qsizetype i = 0; i < len; ++i)
- dst[i] = d_ptr->colorSpaceOut->lut[1]->u16FromLinearF32(buffer[i].y);
+ dst[i] = d_ptr->colorSpaceOut->lut[0]->u16FromLinearF32(buffer[i].y);
}
static constexpr qsizetype WorkBlockSize = 256;
@@ -1218,175 +1258,687 @@ private:
alignas(T) char data[sizeof(T) * Count];
};
+void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
+{
+ const float f = 1.0f / 255.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const uint p = src[i];
+ buffer[i].x = qRed(p) * f;
+ buffer[i].y = qGreen(p) * f;
+ buffer[i].z = qBlue(p) * f;
+ }
+}
+
+void loadUnpremultipliedLUT(QColorVector *buffer, const QCmyk32 *src, const qsizetype len)
+{
+ const float f = 1.0f / 255.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const QCmyk32 p = src[i];
+ buffer[i].x = (p.cyan() * f);
+ buffer[i].y = (p.magenta() * f);
+ buffer[i].z = (p.yellow() * f);
+ buffer[i].w = (p.black() * f);
+ }
+}
+
+void loadUnpremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len)
+{
+ const float f = 1.0f / 65535.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ buffer[i].x = src[i].red() * f;
+ buffer[i].y = src[i].green() * f;
+ buffer[i].z = src[i].blue() * f;
+ }
+}
+
+void loadUnpremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ buffer[i].x = src[i].r;
+ buffer[i].y = src[i].g;
+ buffer[i].z = src[i].b;
+ }
+}
+
+void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const uint p = src[i];
+ const float f = 1.0f / qAlpha(p);
+ buffer[i].x = (qRed(p) * f);
+ buffer[i].y = (qGreen(p) * f);
+ buffer[i].z = (qBlue(p) * f);
+ }
+}
+
+void loadPremultipliedLUT(QColorVector *, const QCmyk32 *, const qsizetype)
+{
+ Q_UNREACHABLE();
+}
+
+void loadPremultipliedLUT(QColorVector *buffer, const QRgba64 *src, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float f = 1.0f / src[i].alpha();
+ buffer[i].x = (src[i].red() * f);
+ buffer[i].y = (src[i].green() * f);
+ buffer[i].z = (src[i].blue() * f);
+ }
+}
+
+void loadPremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float f = 1.0f / src[i].a;
+ buffer[i].x = src[i].r * f;
+ buffer[i].y = src[i].g * f;
+ buffer[i].z = src[i].b * f;
+ }
+}
template<typename T>
-void QColorTransformPrivate::apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const
+static void storeUnpremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len)
{
- if (!colorMatrix.isValid())
- return;
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 255.f;
+ const int g = buffer[i].y * 255.f;
+ const int b = buffer[i].z * 255.f;
+ dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
+ }
+}
- updateLutsIn();
- updateLutsOut();
+template<>
+void storeUnpremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 255.f;
+ const int g = buffer[i].y * 255.f;
+ const int b = buffer[i].z * 255.f;
+ dst[i] = (src[i] & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
+ }
+}
- bool doApplyMatrix = !colorMatrix.isIdentity();
- constexpr bool DoClip = !std::is_same_v<T, QRgbaFloat16> && !std::is_same_v<T, QRgbaFloat32>;
- QUninitialized<QColorVector, WorkBlockSize> buffer;
+template<typename T>
+void storeUnpremultipliedLUT(QCmyk32 *dst, const T *, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int c = buffer[i].x * 255.f;
+ const int m = buffer[i].y * 255.f;
+ const int y = buffer[i].z * 255.f;
+ const int k = buffer[i].w * 255.f;
+ dst[i] = QCmyk32(c, m, y, k);
+ }
+}
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- if (flags & InputPremultiplied)
- loadPremultiplied(buffer, src + i, len, this);
- else
- loadUnpremultiplied(buffer, src + i, len, this);
+template<typename T>
+static void storeUnpremultipliedLUT(QRgba64 *dst, const T *,
+ const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 65535.f;
+ const int g = buffer[i].y * 65535.f;
+ const int b = buffer[i].z * 65535.f;
+ dst[i] = qRgba64(r, g, b, 65535);
+ }
+}
- if (doApplyMatrix)
- applyMatrix<DoClip>(buffer, len, colorMatrix);
+template<>
+void storeUnpremultipliedLUT(QRgba64 *dst, const QRgb *src,
+ const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int a = qAlpha(src[i]) * 257;
+ const int r = buffer[i].x * 65535.f;
+ const int g = buffer[i].y * 65535.f;
+ const int b = buffer[i].z * 65535.f;
+ dst[i] = qRgba64(r, g, b, a);
+ }
+}
- if (flags & InputOpaque)
- storeOpaque(dst + i, src + i, buffer, len, this);
- else if (flags & OutputPremultiplied)
- storePremultiplied(dst + i, src + i, buffer, len, this);
- else
- storeUnpremultiplied(dst + i, src + i, buffer, len, this);
+template<>
+void storeUnpremultipliedLUT(QRgba64 *dst, const QRgba64 *src,
+ const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 65535.f;
+ const int g = buffer[i].y * 65535.f;
+ const int b = buffer[i].z * 65535.f;
+ dst[i] = qRgba64(r, g, b, src[i].alpha());
+ }
+}
- i += len;
+template<typename T>
+static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const T *src,
+ const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float r = buffer[i].x;
+ const float g = buffer[i].y;
+ const float b = buffer[i].z;
+ dst[i] = QRgbaFloat32{r, g, b, getAlphaF(src[i])};
}
}
-template<typename D, typename S>
-void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
+template<typename T>
+static void storePremultipliedLUT(QRgb *, const T *, const QColorVector *, const qsizetype);
+
+template<>
+void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
{
- if (!colorMatrix.isValid())
- return;
+ for (qsizetype i = 0; i < len; ++i) {
+ const int a = qAlpha(src[i]);
+ const int r = buffer[i].x * a;
+ const int g = buffer[i].y * a;
+ const int b = buffer[i].z * a;
+ dst[i] = (src[i] & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
+ }
+}
- updateLutsIn();
- updateLutsOut();
+template<>
+void storePremultipliedLUT(QRgb *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 255.f;
+ const int g = buffer[i].y * 255.f;
+ const int b = buffer[i].z * 255.f;
+ dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
+ }
+}
- QUninitialized<QColorVector, WorkBlockSize> buffer;
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- if (flags & InputPremultiplied)
- loadPremultiplied(buffer, src + i, len, this);
- else
- loadUnpremultiplied(buffer, src + i, len, this);
+template<typename T>
+static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector *buffer, const qsizetype len)
+{
+ storeUnpremultipliedLUT(dst, src, buffer, len);
+}
- applyMatrix(buffer, len, colorMatrix);
+template<typename T>
+static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype);
- storeGray(dst + i, src + i, buffer, len, this);
+template<>
+void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int a = qAlpha(src[i]) * 257;
+ const int r = buffer[i].x * a;
+ const int g = buffer[i].y * a;
+ const int b = buffer[i].z * a;
+ dst[i] = qRgba64(r, g, b, a);
+ }
+}
- i += len;
+template<>
+void storePremultipliedLUT(QRgba64 *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 65535.f;
+ const int g = buffer[i].y * 65535.f;
+ const int b = buffer[i].z * 65535.f;
+ dst[i] = qRgba64(r, g, b, 65535);
}
}
-/*!
- \internal
- \enum QColorTransformPrivate::TransformFlag
+template<>
+void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int a = src[i].alpha();
+ const int r = buffer[i].x * a;
+ const int g = buffer[i].y * a;
+ const int b = buffer[i].z * a;
+ dst[i] = qRgba64(r, g, b, a);
+ }
+}
- Defines how the transform is to be applied.
+template<typename T>
+static void storePremultipliedLUT(QRgbaFloat32 *dst, const T *src, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const float a = getAlphaF(src[i]);
+ const float r = buffer[i].x * a;
+ const float g = buffer[i].y * a;
+ const float b = buffer[i].z * a;
+ dst[i] = QRgbaFloat32{r, g, b, a};
+ }
+}
- \value Unpremultiplied The input and output should both be unpremultiplied.
- \value InputOpaque The input is guaranteed to be opaque.
- \value InputPremultiplied The input is premultiplied.
- \value OutputPremultiplied The output should be premultiplied.
- \value Premultiplied Both input and output should both be premultiplied.
-*/
+static void visitElement(const QColorSpacePrivate::TransferElement &element, QColorVector *buffer, const qsizetype len)
+{
+ const bool doW = element.trc[3].isValid();
+ for (qsizetype i = 0; i < len; ++i) {
+ buffer[i].x = element.trc[0].apply(buffer[i].x);
+ buffer[i].y = element.trc[1].apply(buffer[i].y);
+ buffer[i].z = element.trc[2].apply(buffer[i].z);
+ if (doW)
+ buffer[i].w = element.trc[3].apply(buffer[i].w);
+ }
+}
+
+static void visitElement(const QColorMatrix &element, QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i)
+ buffer[i] = element.map(buffer[i]);
+}
+
+static void visitElement(const QColorVector &offset, QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i)
+ buffer[i] += offset;
+}
+
+static void visitElement(const QColorCLUT &element, QColorVector *buffer, const qsizetype len)
+{
+ if (element.isEmpty())
+ return;
+ for (qsizetype i = 0; i < len; ++i)
+ buffer[i] = element.apply(buffer[i]);
+}
/*!
\internal
- Prepares a color transformation for fast application. You do not need to
- call this explicitly as it will be called implicitly on the first transforms, but
- if you want predictable performance on the first transforms, you can perform it
- in advance.
-
- \sa QColorTransform::map(), apply()
*/
-void QColorTransformPrivate::prepare()
+QColorVector QColorTransformPrivate::map(QColorVector c) const
{
- updateLutsIn();
- updateLutsOut();
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ if (colorSpaceIn->lut.generated.loadAcquire()) {
+ c.x = colorSpaceIn->lut[0]->toLinear(c.x);
+ c.y = colorSpaceIn->lut[1]->toLinear(c.y);
+ c.z = colorSpaceIn->lut[2]->toLinear(c.z);
+ } else {
+ c.x = colorSpaceIn->trc[0].apply(c.x);
+ c.y = colorSpaceIn->trc[1].apply(c.y);
+ c.z = colorSpaceIn->trc[2].apply(c.z);
+ }
+ c = colorMatrix.map(c);
+ } else {
+ // Do element based conversion
+ for (auto &&element : colorSpaceIn->mAB)
+ std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
+ }
+ c.x = std::clamp(c.x, 0.0f, 1.0f);
+ c.y = std::clamp(c.y, 0.0f, 1.0f);
+ c.z = std::clamp(c.z, 0.0f, 1.0f);
+
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab)
+ c = c.xyzToLab();
+ else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab)
+ c = c.labToXyz();
+
+ if (colorSpaceOut->isThreeComponentMatrix()) {
+ if (!colorSpaceIn->isThreeComponentMatrix()) {
+ c = colorMatrix.map(c);
+ c.x = std::clamp(c.x, 0.0f, 1.0f);
+ c.y = std::clamp(c.y, 0.0f, 1.0f);
+ c.z = std::clamp(c.z, 0.0f, 1.0f);
+ }
+ if (colorSpaceOut->lut.generated.loadAcquire()) {
+ c.x = colorSpaceOut->lut[0]->fromLinear(c.x);
+ c.y = colorSpaceOut->lut[1]->fromLinear(c.y);
+ c.z = colorSpaceOut->lut[2]->fromLinear(c.z);
+ } else {
+ c.x = colorSpaceOut->trc[0].applyInverse(c.x);
+ c.y = colorSpaceOut->trc[1].applyInverse(c.y);
+ c.z = colorSpaceOut->trc[2].applyInverse(c.z);
+ }
+ } else {
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
+ c.x = std::clamp(c.x, 0.0f, 1.0f);
+ c.y = std::clamp(c.y, 0.0f, 1.0f);
+ c.z = std::clamp(c.z, 0.0f, 1.0f);
+ }
+ return c;
}
/*!
\internal
- Applies the color transformation on \a count QRgb pixels starting from
- \a src and stores the result in \a dst.
-
- Thread-safe if prepare() has been called first.
+*/
+QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
+{
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ c.x = colorSpaceIn->trc[0].applyExtended(c.x);
+ c.y = colorSpaceIn->trc[1].applyExtended(c.y);
+ c.z = colorSpaceIn->trc[2].applyExtended(c.z);
+ c = colorMatrix.map(c);
+ } else {
+ // Do element based conversion
+ for (auto &&element : colorSpaceIn->mAB)
+ std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
+ }
- Assumes unpremultiplied data by default. Set \a flags to change defaults.
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab)
+ c = c.xyzToLab();
+ else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab)
+ c = c.labToXyz();
+
+ if (colorSpaceOut->isThreeComponentMatrix()) {
+ if (!colorSpaceIn->isThreeComponentMatrix())
+ c = colorMatrix.map(c);
+ c.x = colorSpaceOut->trc[0].applyInverseExtended(c.x);
+ c.y = colorSpaceOut->trc[1].applyInverseExtended(c.y);
+ c.z = colorSpaceOut->trc[2].applyInverseExtended(c.z);
+ } else {
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&c](auto &&elm) { visitElement(elm, &c, 1); }, element);
+ }
+ return c;
+}
- \sa prepare()
-*/
-void QColorTransformPrivate::apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const
+template<typename S>
+void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
- apply<QRgb>(dst, src, count, flags);
+ // Avoid compiling this part for S=QCmyk32:
+ if constexpr (!std::is_same_v<S, QCmyk32>) {
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ if (flags & InputPremultiplied)
+ loadPremultiplied(buffer, src, len, this);
+ else
+ loadUnpremultiplied(buffer, src, len, this);
+
+ if (!colorSpaceOut->isThreeComponentMatrix())
+ applyMatrix<DoClamp>(buffer, len, colorSpaceIn->toXyz);
+ return;
+ }
+ }
+ Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix());
+
+ if (flags & InputPremultiplied)
+ loadPremultipliedLUT(buffer, src, len);
+ else
+ loadUnpremultipliedLUT(buffer, src, len);
+
+ if constexpr (std::is_same_v<S, QRgbaFloat16> || std::is_same_v<S, QRgbaFloat32>)
+ clampIfNeeded<DoClamp>(buffer, len);
+
+ // Do element based conversion
+ for (auto &&element : colorSpaceIn->mAB)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
+}
+
+template<typename D, typename S>
+void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
+{
+ constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
+ // Avoid compiling this part for D=QCmyk32:
+ if constexpr (!std::is_same_v<D, QCmyk32>) {
+ if (colorSpaceOut->isThreeComponentMatrix()) {
+ applyMatrix<doClamp>(buffer, len, colorMatrix);
+
+ if constexpr (std::is_same_v<S, QCmyk32>) {
+ storeOpaque(dst, buffer, len, this);
+ } else {
+ if (flags & InputOpaque)
+ storeOpaque(dst, buffer, len, this);
+ else if (flags & OutputPremultiplied)
+ storePremultiplied(dst, src, buffer, len, this);
+ else
+ storeUnpremultiplied(dst, src, buffer, len, this);
+ }
+ return;
+ }
+ }
+ Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix());
+
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
+
+ clampIfNeeded<doClamp>(buffer, len);
+
+ if (flags & OutputPremultiplied)
+ storePremultipliedLUT(dst, src, buffer, len);
+ else
+ storeUnpremultipliedLUT(dst, src, buffer, len);
}
/*!
\internal
- Applies the color transformation on \a count QRgba64 pixels starting from
- \a src and stores the result in \a dst.
+ Adapt Profile Connecting Color spaces.
+*/
+void QColorTransformPrivate::pcsAdapt(QColorVector *buffer, qsizetype count) const
+{
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].xyzToLab();
+ } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].labToXyz();
+ }
+}
- Thread-safe if prepare() has been called first.
+/*!
+ \internal
+ Applies the color transformation on \a count S pixels starting from
+ \a src and stores the result in \a dst as D pixels .
Assumes unpremultiplied data by default. Set \a flags to change defaults.
\sa prepare()
*/
-void QColorTransformPrivate::apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const
+template<typename D, typename S>
+void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
- apply<QRgba64>(dst, src, count, flags);
+ if (colorSpaceIn->isThreeComponentMatrix())
+ updateLutsIn();
+ if (colorSpaceOut->isThreeComponentMatrix())
+ updateLutsOut();
+
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+
+ applyConvertIn(src + i, buffer, len, flags);
+
+ pcsAdapt(buffer, len);
+
+ applyConvertOut(dst + i, src + i, buffer, len, flags);
+
+ i += len;
+ }
}
/*!
\internal
- Applies the color transformation on \a count QRgbaFloat32 pixels starting from
- \a src and stores the result in \a dst.
+ Is to be called on a color-transform to XYZ, returns only luminance values.
+
+ */
+template<typename D, typename S>
+void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
+{
+ Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
+ updateLutsOut();
+ if (!colorSpaceIn->isThreeComponentMatrix()) {
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
- Thread-safe if prepare() has been called first.
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
- Assumes unpremultiplied data by default. Set \a flags to change defaults.
+ applyConvertIn(src, buffer, len, flags);
- \sa prepare()
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
+ for (qsizetype j = 0; j < len; ++j)
+ buffer[j] = buffer[j].xyzToLab();
+ } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
+ for (qsizetype j = 0; j < len; ++j)
+ buffer[j] = buffer[j].labToXyz();
+ }
+
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+ storeOpaque(dst + i, buffer, len, this);
+
+ i += len;
+ }
+ return;
+ }
+ if constexpr (!std::is_same_v<S, QCmyk32>) {
+ if (!colorMatrix.isValid())
+ return;
+
+ updateLutsIn();
+
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
+
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+ if (flags & InputPremultiplied)
+ loadPremultiplied(buffer, src + i, len, this);
+ else
+ loadUnpremultiplied(buffer, src + i, len, this);
+
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+
+ storeOpaque(dst + i, buffer, len, this);
+
+ i += len;
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
+}
+
+/*!
+ \internal
*/
-void QColorTransformPrivate::apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count,
- TransformFlags flags) const
+template<typename D, typename S>
+void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const
{
- apply<QRgbaFloat32>(dst, src, count, flags);
+ Q_ASSERT(colorSpaceIn->isThreeComponentMatrix());
+ updateLutsIn();
+ if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32> || std::is_same_v<D, QCmyk32>) {
+ if (!colorSpaceOut->isThreeComponentMatrix()) {
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
+
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+ loadGray(buffer, src + i, len, this);
+
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
+ for (qsizetype j = 0; j < len; ++j)
+ buffer[j] = buffer[j].xyzToLab();
+ } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
+ for (qsizetype j = 0; j < len; ++j)
+ buffer[j] = buffer[j].labToXyz();
+ }
+
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
+
+ clampIfNeeded<DoClamp>(buffer, len);
+
+ storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque
+
+ i += len;
+ }
+ return;
+ }
+ }
+ Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
+ if constexpr (!std::is_same_v<D, QCmyk32>) {
+ if (!colorMatrix.isValid())
+ return;
+
+ updateLutsOut();
+
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
+
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
+ loadGray(buffer, src + i, len, this);
+
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
+
+ storeOpaque(dst + i, buffer, len, this);
+ i += len;
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
}
/*!
\internal
- Is to be called on a color-transform to XYZ, returns only luminance values.
+ \enum QColorTransformPrivate::TransformFlag
+
+ Defines how the transform should handle alpha values.
+ \value Unpremultiplied The input and output should both be unpremultiplied.
+ \value InputOpaque The input is guaranteed to be opaque.
+ \value InputPremultiplied The input is premultiplied.
+ \value OutputPremultiplied The output should be premultiplied.
+ \value Premultiplied Both input and output should both be premultiplied.
*/
-void QColorTransformPrivate::apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const
-{
- applyReturnGray<quint8, QRgb>(dst, src, count, flags);
-}
/*!
\internal
- Is to be called on a color-transform to XYZ, returns only luminance values.
+ Prepares a color transformation for fast application. You do not need to
+ call this explicitly as it will be called implicitly on the first transforms, but
+ if you want predictable performance on the first transforms, you can perform it
+ in advance.
+ \sa QColorTransform::map(), apply()
*/
-void QColorTransformPrivate::apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const
+void QColorTransformPrivate::prepare()
{
- applyReturnGray<quint16, QRgba64>(dst, src, count, flags);
+ updateLutsIn();
+ updateLutsOut();
}
+// Only allow versions increasing precision
+template void QColorTransformPrivate::applyReturnGray<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyReturnGray<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyReturnGray<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyReturnGray<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QCmyk32, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::applyGray<QRgba64, quint16>(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+
+template void QColorTransformPrivate::apply<QRgb, QRgb>(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgb, QCmyk32>(QRgb *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, QRgb>(QCmyk32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, QCmyk32>(QCmyk32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, QRgba64>(QCmyk32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, QRgbaFloat32>(QCmyk32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgba64, QRgb>(QRgba64 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgba64, QCmyk32>(QRgba64 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgba64, QRgba64>(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgbaFloat32, QRgb>(QRgbaFloat32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgbaFloat32, QCmyk32>(QRgbaFloat32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgbaFloat32, QRgba64>(QRgbaFloat32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgbaFloat32, QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const;
/*!
\internal
*/
bool QColorTransformPrivate::isIdentity() const
{
+ if (colorSpaceIn == colorSpaceOut)
+ return true;
if (!colorMatrix.isIdentity())
return false;
if (colorSpaceIn && colorSpaceOut) {
+ if (colorSpaceIn->equals(colorSpaceOut.constData()))
+ return true;
+ if (!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix())
+ return false;
if (colorSpaceIn->transferFunction != colorSpaceOut->transferFunction)
return false;
if (colorSpaceIn->transferFunction == QColorSpace::TransferFunction::Custom) {
@@ -1395,6 +1947,10 @@ bool QColorTransformPrivate::isIdentity() const
&& colorSpaceIn->trc[2] == colorSpaceOut->trc[2];
}
} else {
+ if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix())
+ return false;
+ if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix())
+ return false;
if (colorSpaceIn && colorSpaceIn->transferFunction != QColorSpace::TransferFunction::Linear)
return false;
if (colorSpaceOut && colorSpaceOut->transferFunction != QColorSpace::TransferFunction::Linear)
diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h
index e5be45bb20..c74fe100eb 100644
--- a/src/gui/painting/qcolortransform_p.h
+++ b/src/gui/painting/qcolortransform_p.h
@@ -22,6 +22,7 @@
#include <QtGui/qrgbafloat.h>
QT_BEGIN_NAMESPACE
+class QCmyk32;
class QColorTransformPrivate : public QSharedData
{
@@ -47,19 +48,22 @@ public:
};
Q_DECLARE_FLAGS(TransformFlags, TransformFlag)
- void apply(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
- void apply(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
- void apply(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count,
- TransformFlags flags = Unpremultiplied) const;
- void apply(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
- void apply(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags = Unpremultiplied) const;
-
- template<typename T>
- void apply(T *dst, const T *src, qsizetype count, TransformFlags flags) const;
+ QColorVector map(QColorVector color) const;
+ QColorVector mapExtended(QColorVector color) const;
template<typename D, typename S>
+ void apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+ template<typename D, typename S>
+ void applyGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+ template<typename D, typename S>
void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
+private:
+ void pcsAdapt(QColorVector *buffer, qsizetype len) const;
+ template<typename S>
+ void applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
+ template<typename D, typename S>
+ void applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrc_p.h b/src/gui/painting/qcolortrc_p.h
index 9e879bb3e2..d1ad1987df 100644
--- a/src/gui/painting/qcolortrc_p.h
+++ b/src/gui/painting/qcolortrc_p.h
@@ -21,17 +21,15 @@
QT_BEGIN_NAMESPACE
-
-// Defines an ICC TRC (Tone Reproduction Curve)
+// Defines a TRC (Tone Reproduction Curve)
class Q_GUI_EXPORT QColorTrc
{
public:
- QColorTrc() noexcept : m_type(Type::Uninitialized)
- { }
- QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun)
- { }
- QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table)
- { }
+ QColorTrc() noexcept : m_type(Type::Uninitialized) { }
+ QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun) { }
+ QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table) { }
+ QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::Function), m_fun(std::move(fun)) { }
+ QColorTrc(QColorTransferTable &&table) noexcept : m_type(Type::Table), m_table(std::move(table)) { }
enum class Type {
Uninitialized,
@@ -39,9 +37,10 @@ public:
Table
};
- bool isLinear() const
+ bool isIdentity() const
{
- return m_type == Type::Uninitialized || (m_type == Type::Function && m_fun.isLinear());
+ return (m_type == Type::Function && m_fun.isIdentity())
+ || (m_type == Type::Table && m_table.isIdentity());
}
bool isValid() const
{
diff --git a/src/gui/painting/qcolortrclut.cpp b/src/gui/painting/qcolortrclut.cpp
index 6f1cacea75..8a7673bc00 100644
--- a/src/gui/painting/qcolortrclut.cpp
+++ b/src/gui/painting/qcolortrclut.cpp
@@ -13,43 +13,80 @@ std::shared_ptr<QColorTrcLut> QColorTrcLut::create()
return std::make_shared<Access>();
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(qreal gamma)
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(qreal gamma, Direction dir)
{
auto cp = create();
+ cp->setFromGamma(gamma, dir);
+ return cp;
+}
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), gamma) * (255 * 256)));
- cp->m_fromLinear[i] = ushort(qRound(qPow(i / qreal(255 * 16), qreal(1) / gamma) * (255 * 256)));
- }
-
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun, Direction dir)
+{
+ auto cp = create();
+ cp->setFromTransferFunction(fun, dir);
return cp;
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun)
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferTable(const QColorTransferTable &table, Direction dir)
{
auto cp = create();
- QColorTransferFunction inv = fun.inverted();
+ cp->setFromTransferTable(table, dir);
+ return cp;
+}
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(255 * 16)) * (255 * 256)));
- cp->m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(255 * 16)) * (255 * 256)));
+void QColorTrcLut::setFromGamma(qreal gamma, Direction dir)
+{
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), gamma) * (255 * 256)));
}
- return cp;
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), qreal(1) / gamma) * (255 * 256)));
+ }
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferTable(const QColorTransferTable &table)
+void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Direction dir)
{
- auto cp = create();
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(Resolution)) * (255 * 256)));
+ }
- float minInverse = 0.0f;
- for (int i = 0; i <= (255 * 16); ++i) {
- cp->m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(255 * 16)) * (255 * 256)), 65280));
- minInverse = table.applyInverse(i / qreal(255 * 16), minInverse);
- cp->m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280));
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ QColorTransferFunction inv = fun.inverted();
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(Resolution)) * (255 * 256)));
}
+}
- return cp;
+void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direction dir)
+{
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(Resolution)) * (255 * 256)), 65280));
+ }
+
+ if (dir & FromLinear) {
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ float minInverse = 0.0f;
+ for (int i = 0; i <= Resolution; ++i) {
+ minInverse = table.applyInverse(i / qreal(Resolution), minInverse);
+ m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280));
+ }
+ }
}
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrclut_p.h b/src/gui/painting/qcolortrclut_p.h
index c6b73d9f69..3ebab42809 100644
--- a/src/gui/painting/qcolortrclut_p.h
+++ b/src/gui/painting/qcolortrclut_p.h
@@ -36,9 +36,22 @@ class QColorTransferTable;
class Q_GUI_EXPORT QColorTrcLut
{
public:
- static std::shared_ptr<QColorTrcLut> fromGamma(qreal gamma);
- static std::shared_ptr<QColorTrcLut> fromTransferFunction(const QColorTransferFunction &transfn);
- static std::shared_ptr<QColorTrcLut> fromTransferTable(const QColorTransferTable &transTable);
+ static constexpr uint32_t ShiftUp = 4; // Amount to shift up from 1->255
+ static constexpr uint32_t ShiftDown = (8 - ShiftUp); // Amount to shift down from 1->65280
+ static constexpr qsizetype Resolution = (1 << ShiftUp) * 255; // Number of entries in table
+
+ enum Direction {
+ ToLinear = 1,
+ FromLinear = 2,
+ BiLinear = ToLinear | FromLinear
+ };
+
+ static std::shared_ptr<QColorTrcLut> fromGamma(qreal gamma, Direction dir = BiLinear);
+ static std::shared_ptr<QColorTrcLut> fromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear);
+ static std::shared_ptr<QColorTrcLut> fromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear);
+ void setFromGamma(qreal gamma, Direction dir = BiLinear);
+ void setFromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear);
+ void setFromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear);
// The following methods all convert opaque or unpremultiplied colors:
@@ -47,7 +60,7 @@ public:
#if defined(__SSE2__)
__m128i v = _mm_cvtsi32_si128(rgb32);
v = _mm_unpacklo_epi8(v, _mm_setzero_si128());
- const __m128i vidx = _mm_slli_epi16(v, 4);
+ const __m128i vidx = _mm_slli_epi16(v, ShiftUp);
const int ridx = _mm_extract_epi16(vidx, 2);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 0);
@@ -62,7 +75,7 @@ public:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint8x8_t v8 = vreinterpret_u8_u32(vmov_n_u32(rgb32));
uint16x4_t v16 = vget_low_u16(vmovl_u8(v8));
- const uint16x4_t vidx = vshl_n_u16(v16, 4);
+ const uint16x4_t vidx = vshl_n_u16(v16, ShiftUp);
const int ridx = vget_lane_u16(vidx, 2);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 0);
@@ -73,9 +86,9 @@ public:
v16 = vadd_u16(v16, vshr_n_u16(v16, 8));
return QRgba64::fromRgba64(vget_lane_u64(vreinterpret_u64_u16(v16), 0));
#else
- uint r = m_toLinear[qRed(rgb32) << 4];
- uint g = m_toLinear[qGreen(rgb32) << 4];
- uint b = m_toLinear[qBlue(rgb32) << 4];
+ uint r = m_toLinear[qRed(rgb32) << ShiftUp];
+ uint g = m_toLinear[qGreen(rgb32) << ShiftUp];
+ uint b = m_toLinear[qBlue(rgb32) << ShiftUp];
r = r + (r >> 8);
g = g + (g >> 8);
b = b + (b >> 8);
@@ -86,30 +99,30 @@ public:
QRgb toLinear(QRgb rgb32) const
{
- return convertWithTable(rgb32, m_toLinear);
+ return convertWithTable(rgb32, m_toLinear.get());
}
QRgba64 toLinear(QRgba64 rgb64) const
{
- return convertWithTable(rgb64, m_toLinear);
+ return convertWithTable(rgb64, m_toLinear.get());
}
float u8ToLinearF32(int c) const
{
- ushort v = m_toLinear[c << 4];
+ ushort v = m_toLinear[c << ShiftUp];
return v * (1.0f / (255*256));
}
float u16ToLinearF32(int c) const
{
c -= (c >> 8);
- ushort v = m_toLinear[c >> 4];
+ ushort v = m_toLinear[c >> ShiftDown];
return v * (1.0f / (255*256));
}
float toLinear(float f) const
{
- ushort v = m_toLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_toLinear[(int)(f * Resolution + 0.5f)];
return v * (1.0f / (255*256));
}
@@ -118,7 +131,7 @@ public:
#if defined(__SSE2__)
__m128i v = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(&rgb64));
v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8));
- const __m128i vidx = _mm_srli_epi16(v, 4);
+ const __m128i vidx = _mm_srli_epi16(v, ShiftDown);
const int ridx = _mm_extract_epi16(vidx, 0);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 2);
@@ -132,7 +145,7 @@ public:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64));
v = vsub_u16(v, vshr_n_u16(v, 8));
- const uint16x4_t vidx = vshr_n_u16(v, 4);
+ const uint16x4_t vidx = vshr_n_u16(v, ShiftDown);
const int ridx = vget_lane_u16(vidx, 0);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 2);
@@ -151,56 +164,56 @@ public:
g = g - (g >> 8);
b = b - (b >> 8);
a = (a + 0x80) >> 8;
- r = (m_fromLinear[r >> 4] + 0x80) >> 8;
- g = (m_fromLinear[g >> 4] + 0x80) >> 8;
- b = (m_fromLinear[b >> 4] + 0x80) >> 8;
+ r = (m_fromLinear[r >> ShiftDown] + 0x80) >> 8;
+ g = (m_fromLinear[g >> ShiftDown] + 0x80) >> 8;
+ b = (m_fromLinear[b >> ShiftDown] + 0x80) >> 8;
return (a << 24) | (r << 16) | (g << 8) | b;
#endif
}
QRgb fromLinear(QRgb rgb32) const
{
- return convertWithTable(rgb32, m_fromLinear);
+ return convertWithTable(rgb32, m_fromLinear.get());
}
QRgba64 fromLinear(QRgba64 rgb64) const
{
- return convertWithTable(rgb64, m_fromLinear);
+ return convertWithTable(rgb64, m_fromLinear.get());
}
int u8FromLinearF32(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return (v + 0x80) >> 8;
}
int u16FromLinearF32(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return v + (v >> 8);
}
float fromLinear(float f) const
{
- ushort v = m_fromLinear[(int)(f * (255 * 16) + 0.5f)];
+ ushort v = m_fromLinear[(int)(f * Resolution + 0.5f)];
return v * (1.0f / (255*256));
}
// We translate to 0-65280 (255*256) instead to 0-65535 to make simple
// shifting an accurate conversion.
- // We translate from 0-4080 (255*16) for the same speed up, and to keep
- // the tables small enough to fit in most inner caches.
- ushort m_toLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
- ushort m_fromLinear[(255 * 16) + 1]; // [0-4080] -> [0-65280]
+ // We translate from 0->Resolution (4080 = 255*16) for the same speed up,
+ // and to keep the tables small enough to fit in most inner caches.
+ std::unique_ptr<ushort[]> m_toLinear; // [0->Resolution] -> [0-65280]
+ std::unique_ptr<ushort[]> m_fromLinear; // [0->Resolution] -> [0-65280]
private:
- QColorTrcLut() { } // force uninitialized members
+ QColorTrcLut() = default;
static std::shared_ptr<QColorTrcLut> create();
Q_ALWAYS_INLINE static QRgb convertWithTable(QRgb rgb32, const ushort *table)
{
- const int r = (table[qRed(rgb32) << 4] + 0x80) >> 8;
- const int g = (table[qGreen(rgb32) << 4] + 0x80) >> 8;
- const int b = (table[qBlue(rgb32) << 4] + 0x80) >> 8;
+ const int r = (table[qRed(rgb32) << ShiftUp] + 0x80) >> 8;
+ const int g = (table[qGreen(rgb32) << ShiftUp] + 0x80) >> 8;
+ const int b = (table[qBlue(rgb32) << ShiftUp] + 0x80) >> 8;
return (rgb32 & 0xff000000) | (r << 16) | (g << 8) | b;
}
Q_ALWAYS_INLINE static QRgba64 convertWithTable(QRgba64 rgb64, const ushort *table)
@@ -208,7 +221,7 @@ private:
#if defined(__SSE2__)
__m128i v = _mm_loadl_epi64(reinterpret_cast<const __m128i *>(&rgb64));
v = _mm_sub_epi16(v, _mm_srli_epi16(v, 8));
- const __m128i vidx = _mm_srli_epi16(v, 4);
+ const __m128i vidx = _mm_srli_epi16(v, ShiftDown);
const int ridx = _mm_extract_epi16(vidx, 2);
const int gidx = _mm_extract_epi16(vidx, 1);
const int bidx = _mm_extract_epi16(vidx, 0);
@@ -222,7 +235,7 @@ private:
#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint16x4_t v = vreinterpret_u16_u64(vmov_n_u64(rgb64));
v = vsub_u16(v, vshr_n_u16(v, 8));
- const uint16x4_t vidx = vshr_n_u16(v, 4);
+ const uint16x4_t vidx = vshr_n_u16(v, ShiftDown);
const int ridx = vget_lane_u16(vidx, 2);
const int gidx = vget_lane_u16(vidx, 1);
const int bidx = vget_lane_u16(vidx, 0);
@@ -238,9 +251,9 @@ private:
r = r - (r >> 8);
g = g - (g >> 8);
b = b - (b >> 8);
- r = table[r >> 4];
- g = table[g >> 4];
- b = table[b >> 4];
+ r = table[r >> ShiftDown];
+ g = table[g >> ShiftDown];
+ b = table[b >> ShiftDown];
r = r + (r >> 8);
g = g + (g >> 8);
b = b + (b >> 8);
diff --git a/src/gui/painting/qcompositionfunctions.cpp b/src/gui/painting/qcompositionfunctions.cpp
index 46e1a812fc..00fd749fe6 100644
--- a/src/gui/painting/qcompositionfunctions.cpp
+++ b/src/gui/painting/qcompositionfunctions.cpp
@@ -397,25 +397,25 @@ struct RgbaFPOperationsSSE2 : public RgbaFPOperationsBase
typedef __m128 OptimalType;
typedef __m128 OptimalScalar;
- static OptimalType load(const Type *ptr)
+ static OptimalType Q_DECL_VECTORCALL load(const Type *ptr)
{
- return _mm_load_ps(reinterpret_cast<const float *>(ptr));
+ return _mm_loadu_ps(reinterpret_cast<const float *>(ptr));
}
- static OptimalType convert(const Type &value)
+ static OptimalType Q_DECL_VECTORCALL convert(const Type &value)
{
return load(&value);
}
- static void store(Type *ptr, OptimalType value)
+ static void Q_DECL_VECTORCALL store(Type *ptr, OptimalType value)
{
- _mm_store_ps(reinterpret_cast<float *>(ptr), value);
+ _mm_storeu_ps(reinterpret_cast<float *>(ptr), value);
}
- static OptimalType add(OptimalType a, OptimalType b)
+ static OptimalType Q_DECL_VECTORCALL add(OptimalType a, OptimalType b)
{
return _mm_add_ps(a, b);
}
// same as above:
// static OptimalScalar add(OptimalScalar a, OptimalScalar b)
- static OptimalType plus(OptimalType a, OptimalType b)
+ static OptimalType Q_DECL_VECTORCALL plus(OptimalType a, OptimalType b)
{
a = _mm_add_ps(a, b);
__m128 aa = _mm_min_ps(a, _mm_set1_ps(1.0f));
@@ -425,37 +425,37 @@ struct RgbaFPOperationsSSE2 : public RgbaFPOperationsBase
a = _mm_shuffle_ps(a, aa, _MM_SHUFFLE(0, 2, 1, 0));
return a;
}
- static OptimalScalar alpha(OptimalType c)
+ static OptimalScalar Q_DECL_VECTORCALL alpha(OptimalType c)
{
return _mm_shuffle_ps(c, c, _MM_SHUFFLE(3, 3, 3, 3));
}
- static OptimalScalar invAlpha(Scalar c)
+ static OptimalScalar Q_DECL_VECTORCALL invAlpha(Scalar c)
{
return _mm_set1_ps(1.0f - float(c));
}
- static OptimalScalar invAlpha(OptimalType c)
+ static OptimalScalar Q_DECL_VECTORCALL invAlpha(OptimalType c)
{
return _mm_sub_ps(_mm_set1_ps(1.0f), alpha(c));
}
- static OptimalScalar scalar(Scalar n)
+ static OptimalScalar Q_DECL_VECTORCALL scalar(Scalar n)
{
return _mm_set1_ps(float(n));
}
- static OptimalType multiplyAlpha(OptimalType val, OptimalScalar a)
+ static OptimalType Q_DECL_VECTORCALL multiplyAlpha(OptimalType val, OptimalScalar a)
{
return _mm_mul_ps(val, a);
}
- static OptimalType interpolate(OptimalType x, OptimalScalar a1, OptimalType y, OptimalScalar a2)
+ static OptimalType Q_DECL_VECTORCALL interpolate(OptimalType x, OptimalScalar a1, OptimalType y, OptimalScalar a2)
{
return add(multiplyAlpha(x, a1), multiplyAlpha(y, a2));
}
- static OptimalType multiplyAlpha8bit(OptimalType val, uint8_t a)
+ static OptimalType Q_DECL_VECTORCALL multiplyAlpha8bit(OptimalType val, uint8_t a)
{
return multiplyAlpha(val, _mm_set1_ps(a * (1.0f / 255.0f)));
}
// same as above:
// static OptimalScalar multiplyAlpha8bit(OptimalScalar a, uint8_t a)
- static OptimalType interpolate8bit(OptimalType x, uint8_t a1, OptimalType y, uint8_t a2)
+ static OptimalType Q_DECL_VECTORCALL interpolate8bit(OptimalType x, uint8_t a1, OptimalType y, uint8_t a2)
{
return add(multiplyAlpha8bit(x, a1), multiplyAlpha8bit(y, a2));
}
diff --git a/src/gui/painting/qcoregraphics.mm b/src/gui/painting/qcoregraphics.mm
index b03ebe55e6..27b46202f5 100644
--- a/src/gui/painting/qcoregraphics.mm
+++ b/src/gui/painting/qcoregraphics.mm
@@ -162,6 +162,9 @@ QT_BEGIN_NAMESPACE
QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
{
+ // ### TODO: add parameter so that we can decide whether to maintain the aspect
+ // ratio of the image (positioning the image inside the pixmap of size \a size),
+ // or whether we want to fill the resulting pixmap by stretching the image.
const NSSize pixmapSize = NSMakeSize(size.width(), size.height());
QPixmap pixmap(pixmapSize.width, pixmapSize.height);
pixmap.fill(Qt::transparent);
@@ -182,6 +185,25 @@ QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
#endif // Q_OS_MACOS
+#ifdef QT_PLATFORM_UIKIT
+
+QImage qt_mac_toQImage(const UIImage *image, QSizeF size)
+{
+ // ### TODO: same as above
+ QImage ret(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
+ ret.fill(Qt::transparent);
+ QMacCGContext ctx(&ret);
+ if (!ctx)
+ return QImage();
+ UIGraphicsPushContext(ctx);
+ const CGRect rect = CGRectMake(0, 0, size.width(), size.height());
+ [image drawInRect:rect];
+ UIGraphicsPopContext();
+ return ret;
+}
+
+#endif // QT_PLATFORM_UIKIT
+
// ---------------------- Colors and Brushes ----------------------
QColor qt_mac_toQColor(CGColorRef color)
@@ -386,11 +408,14 @@ void QMacCGContext::initialize(QPaintDevice *paintDevice)
// Find the underlying QImage of the paint device
switch (int deviceType = paintDevice->devType()) {
case QInternal::Pixmap: {
- auto *platformPixmap = static_cast<QPixmap*>(paintDevice)->handle();
- if (platformPixmap && platformPixmap->classId() == QPlatformPixmap::RasterClass)
- initialize(platformPixmap->buffer());
- else
- qWarning() << "QMacCGContext: Unsupported pixmap class" << platformPixmap->classId();
+ if (auto *platformPixmap = static_cast<QPixmap*>(paintDevice)->handle()) {
+ if (platformPixmap->classId() == QPlatformPixmap::RasterClass)
+ initialize(platformPixmap->buffer());
+ else
+ qWarning() << "QMacCGContext: Unsupported pixmap class" << platformPixmap->classId();
+ } else {
+ qWarning() << "QMacCGContext: Empty platformPixmap";
+ }
break;
}
case QInternal::Image:
diff --git a/src/gui/painting/qcoregraphics_p.h b/src/gui/painting/qcoregraphics_p.h
index f0640ba500..a35f27a730 100644
--- a/src/gui/painting/qcoregraphics_p.h
+++ b/src/gui/painting/qcoregraphics_p.h
@@ -23,16 +23,23 @@
#include <CoreGraphics/CoreGraphics.h>
-#if defined(__OBJC__) && defined(Q_OS_MACOS)
-#include <AppKit/AppKit.h>
-#define HAVE_APPKIT
+#if defined(__OBJC__)
+# if defined(Q_OS_MACOS)
+# include <AppKit/AppKit.h>
+# elif defined(QT_PLATFORM_UIKIT)
+# include <UIKit/UIKit.h>
+# endif
#endif
QT_BEGIN_NAMESPACE
Q_GUI_EXPORT CGBitmapInfo qt_mac_bitmapInfoForImage(const QImage &image);
-#ifdef HAVE_APPKIT
+#ifdef QT_PLATFORM_UIKIT
+Q_GUI_EXPORT QImage qt_mac_toQImage(const UIImage *image, QSizeF size);
+#endif
+
+#ifdef Q_OS_MACOS
Q_GUI_EXPORT QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size);
QT_END_NAMESPACE
@@ -57,7 +64,7 @@ Q_GUI_EXPORT void qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBou
Q_GUI_EXPORT void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform);
-#ifdef HAVE_APPKIT
+#ifdef Q_OS_MACOS
Q_GUI_EXPORT QColor qt_mac_toQColor(const NSColor *color);
Q_GUI_EXPORT QBrush qt_mac_toQBrush(const NSColor *color, QPalette::ColorGroup colorGroup = QPalette::Normal);
#endif
@@ -81,6 +88,4 @@ private:
QT_END_NAMESPACE
-#undef HAVE_APPKIT
-
#endif // QCOREGRAPHICS_P_H
diff --git a/src/gui/painting/qcosmeticstroker.cpp b/src/gui/painting/qcosmeticstroker.cpp
index f555b56adb..a0eddf65d9 100644
--- a/src/gui/painting/qcosmeticstroker.cpp
+++ b/src/gui/painting/qcosmeticstroker.cpp
@@ -364,7 +364,7 @@ void QCosmeticStroker::drawPoints(const QPoint *points, int num)
const QPoint *end = points + num;
while (points < end) {
QPointF p = QPointF(*points) * state->matrix;
- drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
+ drawPixel(this, std::floor(p.x()), std::floor(p.y()), 255);
++points;
}
@@ -377,7 +377,7 @@ void QCosmeticStroker::drawPoints(const QPointF *points, int num)
const QPointF *end = points + num;
while (points < end) {
QPointF p = (*points) * state->matrix;
- drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
+ drawPixel(this, std::floor(p.x()), std::floor(p.y()), 255);
++points;
}
diff --git a/src/gui/painting/qcssutil.cpp b/src/gui/painting/qcssutil.cpp
index db9b7c24fe..caface7d1a 100644
--- a/src/gui/painting/qcssutil.cpp
+++ b/src/gui/painting/qcssutil.cpp
@@ -168,28 +168,28 @@ void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, q
if (width == 1 || (dw1 == 0 && dw2 == 0)) {
p->drawRect(QRectF(x1, y1, x2-x1, y2-y1));
} else { // draw trapezoid
- QPolygonF quad;
+ std::array<QPointF, 4> quad;
switch (edge) {
case TopEdge:
- quad << QPointF(x1, y1) << QPointF(x1 + dw1, y2)
- << QPointF(x2 - dw2, y2) << QPointF(x2, y1);
+ quad = {QPointF(x1, y1), QPointF(x1 + dw1, y2),
+ QPointF(x2 - dw2, y2), QPointF(x2, y1)};
break;
case BottomEdge:
- quad << QPointF(x1 + dw1, y1) << QPointF(x1, y2)
- << QPointF(x2, y2) << QPointF(x2 - dw2, y1);
+ quad = {QPointF(x1 + dw1, y1), QPointF(x1, y2),
+ QPointF(x2, y2), QPointF(x2 - dw2, y1)};
break;
case LeftEdge:
- quad << QPointF(x1, y1) << QPointF(x1, y2)
- << QPointF(x2, y2 - dw2) << QPointF(x2, y1 + dw1);
+ quad = {QPointF(x1, y1), QPointF(x1, y2),
+ QPointF(x2, y2 - dw2), QPointF(x2, y1 + dw1)};
break;
case RightEdge:
- quad << QPointF(x1, y1 + dw1) << QPointF(x1, y2 - dw2)
- << QPointF(x2, y2) << QPointF(x2, y1);
+ quad = {QPointF(x1, y1 + dw1), QPointF(x1, y2 - dw2),
+ QPointF(x2, y2), QPointF(x2, y1)};
break;
default:
break;
}
- p->drawConvexPolygon(quad);
+ p->drawConvexPolygon(quad.data(), static_cast<int>(quad.size()));
}
break;
}
@@ -268,6 +268,7 @@ void qDrawEdge(QPainter *p, qreal x1, qreal y1, qreal x2, qreal y2, qreal dw1, q
default:
break;
}
+ break;
}
default:
break;
diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp
index c6b6a8b6a4..b7a943be38 100644
--- a/src/gui/painting/qdrawhelper.cpp
+++ b/src/gui/painting/qdrawhelper.cpp
@@ -176,7 +176,7 @@ static void QT_FASTCALL convertRGBA32FPMToRGBA64PM(QRgba64 *buffer, int count)
}
}
-static Convert64Func convert64ToRGBA64PM[QImage::NImageFormats] = {
+static Convert64Func convert64ToRGBA64PM[] = {
nullptr,
nullptr,
nullptr,
@@ -213,7 +213,10 @@ static Convert64Func convert64ToRGBA64PM[QImage::NImageFormats] = {
convertRGBA32FPMToRGBA64PM,
convertRGBA32FToRGBA64PM,
convertRGBA32FPMToRGBA64PM,
+ nullptr,
};
+
+static_assert(std::size(convert64ToRGBA64PM) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -247,7 +250,7 @@ static void QT_FASTCALL convertRGBA16FToRGBA32F(QRgbaFloat32 *buffer, const quin
qFloatFromFloat16((float *)buffer, (const qfloat16 *)src, count * 4);
}
-static Convert64ToFPFunc convert64ToRGBA32F[QImage::NImageFormats] = {
+static Convert64ToFPFunc convert64ToRGBA32F[] = {
nullptr,
nullptr,
nullptr,
@@ -284,8 +287,11 @@ static Convert64ToFPFunc convert64ToRGBA32F[QImage::NImageFormats] = {
nullptr,
nullptr,
nullptr,
+ nullptr,
};
+static_assert(std::size(convert64ToRGBA32F) == QImage::NImageFormats);
+
static void convertRGBA32FToRGBA32FPM(QRgbaFloat32 *buffer, int count)
{
for (int i = 0; i < count; ++i)
@@ -353,7 +359,7 @@ static uint *QT_FASTCALL destFetchUndefined(uint *buffer, QRasterBuffer *, int,
return buffer;
}
-static DestFetchProc destFetchProc[QImage::NImageFormats] =
+static DestFetchProc destFetchProc[] =
{
nullptr, // Format_Invalid
destFetchMono, // Format_Mono,
@@ -391,8 +397,11 @@ static DestFetchProc destFetchProc[QImage::NImageFormats] =
destFetch, // Format_RGBX32FPx4
destFetch, // Format_RGBA32FPx4
destFetch, // Format_RGBA32FPx4_Premultiplied
+ destFetch, // Format_CMYK8888
};
+static_assert(std::size(destFetchProc) == QImage::NImageFormats);
+
#if QT_CONFIG(raster_64bit)
static QRgba64 *QT_FASTCALL destFetch64(QRgba64 *buffer, QRasterBuffer *rasterBuffer, int x, int y, int length)
{
@@ -410,7 +419,7 @@ static QRgba64 * QT_FASTCALL destFetch64Undefined(QRgba64 *buffer, QRasterBuffer
return buffer;
}
-static DestFetchProc64 destFetchProc64[QImage::NImageFormats] =
+static DestFetchProc64 destFetchProc64[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -448,7 +457,10 @@ static DestFetchProc64 destFetchProc64[QImage::NImageFormats] =
destFetch64, // Format_RGBX32FPx4
destFetch64, // Format_RGBA32FPx4
destFetch64, // Format_RGBA32FPx4_Premultiplied
+ destFetch64, // Format_CMYK8888
};
+
+static_assert(std::size(destFetchProc64) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -466,7 +478,7 @@ static QRgbaFloat32 *QT_FASTCALL destFetchFPUndefined(QRgbaFloat32 *buffer, QRas
{
return buffer;
}
-static DestFetchProcFP destFetchProcFP[QImage::NImageFormats] =
+static DestFetchProcFP destFetchProcFP[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -504,7 +516,10 @@ static DestFetchProcFP destFetchProcFP[QImage::NImageFormats] =
destFetchRGBFP, // Format_RGBX32FPx4
destFetchFP, // Format_RGBA32FPx4
destFetchRGBFP, // Format_RGBA32FPx4_Premultiplied
+ destFetchFP, // Format_CMYK8888
};
+
+static_assert(std::size(destFetchProcFP) == QImage::NImageFormats);
#endif
/*
@@ -513,9 +528,8 @@ static DestFetchProcFP destFetchProcFP[QImage::NImageFormats] =
*/
static inline QRgb findNearestColor(QRgb color, QRasterBuffer *rbuf)
{
- QRgb color_0 = qPremultiply(rbuf->destColor0);
- QRgb color_1 = qPremultiply(rbuf->destColor1);
- color = qPremultiply(color);
+ const QRgb color_0 = rbuf->destColor0;
+ const QRgb color_1 = rbuf->destColor1;
int r = qRed(color);
int g = qGreen(color);
@@ -630,7 +644,7 @@ static void QT_FASTCALL destStoreGray8(QRasterBuffer *rasterBuffer, int x, int y
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
@@ -654,11 +668,11 @@ static void QT_FASTCALL destStoreGray16(QRasterBuffer *rasterBuffer, int x, int
QRgba64 tmp_line[BufferSize];
for (int k = 0; k < length; ++k)
tmp_line[k] = QRgba64::fromArgb32(buffer[k]);
- tfd->apply(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->applyReturnGray(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
}
}
-static DestStoreProc destStoreProc[QImage::NImageFormats] =
+static DestStoreProc destStoreProc[] =
{
nullptr, // Format_Invalid
destStoreMono, // Format_Mono,
@@ -696,8 +710,11 @@ static DestStoreProc destStoreProc[QImage::NImageFormats] =
destStore, // Format_RGBX32FPx4
destStore, // Format_RGBA32FPx4
destStore, // Format_RGBA32FPx4_Premultiplied
+ destStore, // Format_CMYK8888
};
+static_assert(std::size(destStoreProc) == QImage::NImageFormats);
+
#if QT_CONFIG(raster_64bit)
static void QT_FASTCALL destStore64(QRasterBuffer *rasterBuffer, int x, int y, const QRgba64 *buffer, int length)
{
@@ -732,7 +749,7 @@ static void QT_FASTCALL destStore64Gray8(QRasterBuffer *rasterBuffer, int x, int
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
quint16 gray_line[BufferSize];
- tfd->apply(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->applyReturnGray(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
for (int k = 0; k < length; ++k)
data[k] = qt_div_257(gray_line[k]);
}
@@ -754,11 +771,11 @@ static void QT_FASTCALL destStore64Gray16(QRasterBuffer *rasterBuffer, int x, in
QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb;
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
-static DestStoreProc64 destStoreProc64[QImage::NImageFormats] =
+static DestStoreProc64 destStoreProc64[] =
{
nullptr, // Format_Invalid
nullptr, // Format_Mono,
@@ -796,7 +813,10 @@ static DestStoreProc64 destStoreProc64[QImage::NImageFormats] =
destStore64, // Format_RGBX32FPx4
destStore64, // Format_RGBA32FPx4
destStore64, // Format_RGBA32FPx4_Premultiplied
+ destStore64, // Format_CMYK8888
};
+
+static_assert(std::size(destStoreProc64) == QImage::NImageFormats);
#endif
#if QT_CONFIG(raster_fp)
@@ -3071,7 +3091,7 @@ static const QRgbaFloat32 *QT_FASTCALL fetchTransformedBilinearFP(QRgbaFloat32 *
#endif // QT_CONFIG(raster_fp)
// FetchUntransformed can have more specialized methods added depending on SIMD features.
-static SourceFetchProc sourceFetchUntransformed[QImage::NImageFormats] = {
+static SourceFetchProc sourceFetchUntransformed[] = {
nullptr, // Invalid
fetchUntransformed, // Mono
fetchUntransformed, // MonoLsb
@@ -3108,9 +3128,12 @@ static SourceFetchProc sourceFetchUntransformed[QImage::NImageFormats] = {
fetchUntransformed, // RGBX32Px4
fetchUntransformed, // RGBA32FPx4
fetchUntransformed, // RGBA32FPx4_Premultiplied
+ fetchUntransformed, // CMYK8888
};
-static const SourceFetchProc sourceFetchGeneric[NBlendTypes] = {
+static_assert(std::size(sourceFetchUntransformed) == QImage::NImageFormats);
+
+static const SourceFetchProc sourceFetchGeneric[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPPNone>, // Transformed
@@ -3119,7 +3142,9 @@ static const SourceFetchProc sourceFetchGeneric[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPPNone> // TransformedBilinearTiled
};
-static SourceFetchProc sourceFetchARGB32PM[NBlendTypes] = {
+static_assert(std::size(sourceFetchGeneric) == NBlendTypes);
+
+static SourceFetchProc sourceFetchARGB32PM[] = {
fetchUntransformedARGB32PM, // Untransformed
fetchUntransformedARGB32PM, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP32>, // Transformed
@@ -3128,7 +3153,9 @@ static SourceFetchProc sourceFetchARGB32PM[NBlendTypes] = {
fetchTransformedBilinearARGB32PM<BlendTransformedBilinearTiled> // BilinearTiled
};
-static SourceFetchProc sourceFetchAny16[NBlendTypes] = {
+static_assert(std::size(sourceFetchARGB32PM) == NBlendTypes);
+
+static SourceFetchProc sourceFetchAny16[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP16>, // Transformed
@@ -3137,7 +3164,9 @@ static SourceFetchProc sourceFetchAny16[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPP16> // TransformedBilinearTiled
};
-static SourceFetchProc sourceFetchAny32[NBlendTypes] = {
+static_assert(std::size(sourceFetchAny16) == NBlendTypes);
+
+static SourceFetchProc sourceFetchAny32[] = {
fetchUntransformed, // Untransformed
fetchUntransformed, // Tiled
fetchTransformed<BlendTransformed, QPixelLayout::BPP32>, // Transformed
@@ -3146,6 +3175,8 @@ static SourceFetchProc sourceFetchAny32[NBlendTypes] = {
fetchTransformedBilinear<BlendTransformedBilinearTiled, QPixelLayout::BPP32> // TransformedBilinearTiled
};
+static_assert(std::size(sourceFetchAny32) == NBlendTypes);
+
static inline SourceFetchProc getSourceFetch(TextureBlendType blendType, QImage::Format format)
{
if (format == QImage::Format_RGB32 || format == QImage::Format_ARGB32_Premultiplied)
@@ -3160,7 +3191,7 @@ static inline SourceFetchProc getSourceFetch(TextureBlendType blendType, QImage:
}
#if QT_CONFIG(raster_64bit)
-static const SourceFetchProc64 sourceFetchGeneric64[NBlendTypes] = {
+static const SourceFetchProc64 sourceFetchGeneric64[] = {
fetchUntransformed64, // Untransformed
fetchUntransformed64, // Tiled
fetchTransformed64<BlendTransformed>, // Transformed
@@ -3169,7 +3200,9 @@ static const SourceFetchProc64 sourceFetchGeneric64[NBlendTypes] = {
fetchTransformedBilinear64<BlendTransformedBilinearTiled> // BilinearTiled
};
-static const SourceFetchProc64 sourceFetchRGBA64PM[NBlendTypes] = {
+static_assert(std::size(sourceFetchGeneric64) == NBlendTypes);
+
+static const SourceFetchProc64 sourceFetchRGBA64PM[] = {
fetchUntransformedRGBA64PM, // Untransformed
fetchUntransformedRGBA64PM, // Tiled
fetchTransformed64<BlendTransformed>, // Transformed
@@ -3178,6 +3211,8 @@ static const SourceFetchProc64 sourceFetchRGBA64PM[NBlendTypes] = {
fetchTransformedBilinear64<BlendTransformedBilinearTiled> // BilinearTiled
};
+static_assert(std::size(sourceFetchRGBA64PM) == NBlendTypes);
+
static inline SourceFetchProc64 getSourceFetch64(TextureBlendType blendType, QImage::Format format)
{
if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64_Premultiplied)
@@ -3187,7 +3222,7 @@ static inline SourceFetchProc64 getSourceFetch64(TextureBlendType blendType, QIm
#endif
#if QT_CONFIG(raster_fp)
-static const SourceFetchProcFP sourceFetchGenericFP[NBlendTypes] = {
+static const SourceFetchProcFP sourceFetchGenericFP[] = {
fetchUntransformedFP, // Untransformed
fetchUntransformedFP, // Tiled
fetchTransformedFP<BlendTransformed>, // Transformed
@@ -3196,6 +3231,8 @@ static const SourceFetchProcFP sourceFetchGenericFP[NBlendTypes] = {
fetchTransformedBilinearFP<BlendTransformedBilinearTiled> // BilinearTiled
};
+static_assert(std::size(sourceFetchGenericFP) == NBlendTypes);
+
static inline SourceFetchProcFP getSourceFetchFP(TextureBlendType blendType, QImage::Format /*format*/)
{
return sourceFetchGenericFP[blendType];
@@ -3256,6 +3293,7 @@ public:
static Type null() { return 0; }
static Type fetchSingle(const QGradientData& gradient, qreal v)
{
+ Q_ASSERT(std::isfinite(v));
return qt_gradient_pixel(&gradient, v);
}
static Type fetchSingle(const QGradientData& gradient, int v)
@@ -3276,6 +3314,7 @@ public:
static Type null() { return QRgba64::fromRgba64(0); }
static Type fetchSingle(const QGradientData& gradient, qreal v)
{
+ Q_ASSERT(std::isfinite(v));
return qt_gradient_pixel64(&gradient, v);
}
static Type fetchSingle(const QGradientData& gradient, int v)
@@ -3297,6 +3336,7 @@ public:
static Type null() { return QRgbaFloat32::fromRgba64(0,0,0,0); }
static Type fetchSingle(const QGradientData& gradient, qreal v)
{
+ Q_ASSERT(std::isfinite(v));
return qt_gradient_pixelFP(&gradient, v);
}
static Type fetchSingle(const QGradientData& gradient, int v)
@@ -3414,7 +3454,6 @@ static void QT_FASTCALL getRadialGradientValues(RadialGradientValues *v, const Q
v->sqrfr = data->gradient.radial.focal.radius * data->gradient.radial.focal.radius;
v->a = v->dr * v->dr - v->dx*v->dx - v->dy*v->dy;
- v->inv2a = 1 / (2 * v->a);
v->extended = !qFuzzyIsNull(data->gradient.radial.focal.radius) || v->a <= 0;
}
@@ -3447,7 +3486,13 @@ public:
}
} else {
while (buffer < end) {
- *buffer++ = GradientBase::fetchSingle(data->gradient, qSqrt(det) - b);
+ BlendType result = GradientBase::null();
+ if (det >= 0) {
+ qreal w = qSqrt(det) - b;
+ result = GradientBase::fetchSingle(data->gradient, w);
+ }
+
+ *buffer++ = result;
det += delta_det;
delta_det += delta_delta_det;
@@ -3605,7 +3650,6 @@ static inline Operator getOperator(const QSpanData *data, const QT_FT_Span *span
{
Operator op;
bool solidSource = false;
-
switch(data->type) {
case QSpanData::Solid:
solidSource = data->solidColor.alphaF() >= 1.0f;
@@ -3649,7 +3693,7 @@ static inline Operator getOperator(const QSpanData *data, const QT_FT_Span *span
solidSource = !data->texture.hasAlpha;
op.srcFetch = getSourceFetch(getBlendType(data), data->texture.format);
#if QT_CONFIG(raster_64bit)
- op.srcFetch64 = getSourceFetch64(getBlendType(data), data->texture.format);;
+ op.srcFetch64 = getSourceFetch64(getBlendType(data), data->texture.format);
#endif
#if QT_CONFIG(raster_fp)
op.srcFetchFP = getSourceFetchFP(getBlendType(data), data->texture.format);
@@ -3779,7 +3823,8 @@ static void spanfill_from_first(QRasterBuffer *rasterBuffer, QPixelLayout::BPP b
#define QT_THREAD_PARALLEL_FILLS(function) \
const int segments = (count + 32) / 64; \
QThreadPool *threadPool = QThreadPoolPrivate::qtGuiInstance(); \
- if (segments > 1 && threadPool && !threadPool->contains(QThread::currentThread())) { \
+ if (segments > 1 && qPixelLayouts[data->rasterBuffer->format].bpp >= QPixelLayout::BPP8 \
+ && threadPool && !threadPool->contains(QThread::currentThread())) { \
QSemaphore semaphore; \
int c = 0; \
for (int i = 0; i < segments; ++i) { \
@@ -4965,16 +5010,11 @@ void qBlendTexture(int count, const QT_FT_Span *spans, void *userData)
proc(count, spans, userData);
}
-static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, void *userData)
+static inline bool calculate_fixed_gradient_factors(int count, const QT_FT_Span *spans,
+ const QSpanData *data,
+ const LinearGradientValues &linear,
+ int *pyinc, int *poff)
{
- QSpanData *data = reinterpret_cast<QSpanData *>(userData);
-
- LinearGradientValues linear;
- getLinearGradientValues(&linear, data);
-
- CompositionFunctionSolid funcSolid =
- functionForModeSolid[data->rasterBuffer->compositionMode];
-
/*
The logic for vertical gradient calculations is a mathematically
reduced copy of that in fetchLinearGradient() - which is basically:
@@ -4989,8 +5029,32 @@ static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, voi
This has then been converted to fixed point to improve performance.
*/
const int gss = GRADIENT_STOPTABLE_SIZE - 1;
- int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE);
- int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE));
+ qreal ryinc = linear.dy * data->m22 * gss * FIXPT_SIZE;
+ qreal roff = (linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss * FIXPT_SIZE;
+ const int limit = std::numeric_limits<int>::max() - FIXPT_SIZE;
+ if (count && (std::fabs(ryinc) < limit) && (std::fabs(roff) < limit)
+ && (std::fabs(ryinc * spans->y + roff) < limit)
+ && (std::fabs(ryinc * (spans + count - 1)->y + roff) < limit)) {
+ *pyinc = int(ryinc);
+ *poff = int(roff);
+ return true;
+ }
+ return false;
+}
+
+static bool blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, void *userData)
+{
+ QSpanData *data = reinterpret_cast<QSpanData *>(userData);
+
+ LinearGradientValues linear;
+ getLinearGradientValues(&linear, data);
+
+ CompositionFunctionSolid funcSolid =
+ functionForModeSolid[data->rasterBuffer->compositionMode];
+
+ int yinc(0), off(0);
+ if (!calculate_fixed_gradient_factors(count, spans, data, linear, &yinc, &off))
+ return false;
while (count--) {
int y = spans->y;
@@ -5003,21 +5067,20 @@ static void blend_vertical_gradient_argb(int count, const QT_FT_Span *spans, voi
funcSolid(dst, spans->len, color, spans->coverage);
++spans;
}
+ return true;
}
template<ProcessSpans blend_color>
-static void blend_vertical_gradient(int count, const QT_FT_Span *spans, void *userData)
+static bool blend_vertical_gradient(int count, const QT_FT_Span *spans, void *userData)
{
QSpanData *data = reinterpret_cast<QSpanData *>(userData);
LinearGradientValues linear;
getLinearGradientValues(&linear, data);
- // Based on the same logic as blend_vertical_gradient_argb.
-
- const int gss = GRADIENT_STOPTABLE_SIZE - 1;
- int yinc = int((linear.dy * data->m22 * gss) * FIXPT_SIZE);
- int off = int((((linear.dy * (data->m22 * qreal(0.5) + data->dy) + linear.off) * gss) * FIXPT_SIZE));
+ int yinc(0), off(0);
+ if (!calculate_fixed_gradient_factors(count, spans, data, linear, &yinc, &off))
+ return false;
while (count--) {
int y = spans->y;
@@ -5030,6 +5093,7 @@ static void blend_vertical_gradient(int count, const QT_FT_Span *spans, void *us
blend_color(1, spans, userData);
++spans;
}
+ return true;
}
void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
@@ -5044,8 +5108,8 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
break;
case QImage::Format_RGB32:
case QImage::Format_ARGB32_Premultiplied:
- if (isVerticalGradient)
- return blend_vertical_gradient_argb(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient_argb(count, spans, userData))
+ return;
return blend_src_generic(count, spans, userData);
#if defined(__SSE2__) || defined(__ARM_NEON__) || (Q_PROCESSOR_WORDSIZE == 8)
case QImage::Format_ARGB32:
@@ -5067,8 +5131,8 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
case QImage::Format_RGBA32FPx4_Premultiplied:
#endif
#if QT_CONFIG(raster_64bit)
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic_rgb64>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic_rgb64>(count, spans, userData))
+ return;
return blend_src_generic_rgb64(count, spans, userData);
#endif // QT_CONFIG(raster_64bit)
#if QT_CONFIG(raster_fp)
@@ -5078,13 +5142,13 @@ void qBlendGradient(int count, const QT_FT_Span *spans, void *userData)
case QImage::Format_RGBX32FPx4:
case QImage::Format_RGBA32FPx4:
case QImage::Format_RGBA32FPx4_Premultiplied:
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic_fp>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic_fp>(count, spans, userData))
+ return;
return blend_src_generic_fp(count, spans, userData);
#endif
default:
- if (isVerticalGradient)
- return blend_vertical_gradient<blend_color_generic>(count, spans, userData);
+ if (isVerticalGradient && blend_vertical_gradient<blend_color_generic>(count, spans, userData))
+ return;
return blend_src_generic(count, spans, userData);
}
Q_UNREACHABLE();
@@ -5100,7 +5164,7 @@ inline void qt_bitmapblit_template(QRasterBuffer *rasterBuffer,
const int destStride = rasterBuffer->stride<DST>();
if (mapWidth > 8) {
- while (mapHeight--) {
+ while (--mapHeight >= 0) {
int x0 = 0;
int n = 0;
for (int x = 0; x < mapWidth; x += 8) {
@@ -5130,7 +5194,7 @@ inline void qt_bitmapblit_template(QRasterBuffer *rasterBuffer,
map += mapStride;
}
} else {
- while (mapHeight--) {
+ while (--mapHeight >= 0) {
int x0 = 0;
int n = 0;
for (uchar s = *map; s; s <<= 1) {
@@ -5438,7 +5502,7 @@ void qt_alphamapblit_quint16(QRasterBuffer *rasterBuffer,
if (!clip) {
quint16 *dest = reinterpret_cast<quint16*>(rasterBuffer->scanLine(y)) + x;
const int destStride = rasterBuffer->stride<quint16>();
- while (mapHeight--) {
+ while (--mapHeight >= 0) {
for (int i = 0; i < mapWidth; ++i)
alphamapblend_quint16(map[i], dest, i, c);
dest += destStride;
@@ -5492,7 +5556,7 @@ static void qt_alphamapblit_argb32(QRasterBuffer *rasterBuffer,
if (!clip) {
quint32 *dest = reinterpret_cast<quint32*>(rasterBuffer->scanLine(y)) + x;
- while (mapHeight--) {
+ while (--mapHeight >= 0) {
for (int i = 0; i < mapWidth; ++i) {
const int coverage = map[i];
alphamapblend_argb32(dest + i, coverage, srcColor, c, colorProfile);
@@ -5810,7 +5874,7 @@ static void qt_alphargbblit_argb32(QRasterBuffer *rasterBuffer,
if (!clip) {
quint32 *dst = reinterpret_cast<quint32*>(rasterBuffer->scanLine(y)) + x;
const int destStride = rasterBuffer->stride<quint32>();
- while (mapHeight--) {
+ while (--mapHeight >= 0) {
for (int i = 0; i < mapWidth; ++i) {
const uint coverage = src[i];
alphargbblend_argb32(dst + i, coverage, srcColor, c, colorProfile);
@@ -5954,7 +6018,7 @@ static void qt_rectfill_fp32x4(QRasterBuffer *rasterBuffer,
// Map table for destination image format. Contains function pointers
// for blends of various types unto the destination
-DrawHelper qDrawHelper[QImage::NImageFormats] =
+DrawHelper qDrawHelper[] =
{
// Format_Invalid,
{ nullptr, nullptr, nullptr, nullptr, nullptr },
@@ -6231,6 +6295,8 @@ DrawHelper qDrawHelper[QImage::NImageFormats] =
},
};
+static_assert(std::size(qDrawHelper) == QImage::NImageFormats);
+
#if !defined(Q_PROCESSOR_X86)
void qt_memfill64(quint64 *dest, quint64 color, qsizetype count)
{
diff --git a/src/gui/painting/qdrawhelper_avx2.cpp b/src/gui/painting/qdrawhelper_avx2.cpp
index 17a52402ae..34de69ecf4 100644
--- a/src/gui/painting/qdrawhelper_avx2.cpp
+++ b/src/gui/painting/qdrawhelper_avx2.cpp
@@ -442,14 +442,14 @@ void QT_FASTCALL comp_func_SourceOver_rgbafp_avx2(QRgbaFloat32 *dst, const QRgba
_mm256_storeu_ps((float *)(dst + x), dstVector);
}
if (x < length) {
- __m128 srcVector = _mm_load_ps((float *)(src + x));
- __m128 dstVector = _mm_load_ps((const float *)(dst + x));
+ __m128 srcVector = _mm_loadu_ps((const float *)&src[x]);
+ __m128 dstVector = _mm_loadu_ps((const float *)&dst[x]);
srcVector = _mm_mul_ps(srcVector, constAlphaVector);
__m128 alphaChannel = _mm_permute_ps(srcVector, _MM_SHUFFLE(3, 3, 3, 3));
alphaChannel = _mm_sub_ps(one, alphaChannel);
dstVector = _mm_mul_ps(dstVector, alphaChannel);
dstVector = _mm_add_ps(dstVector, srcVector);
- _mm_store_ps((float *)(dst + x), dstVector);
+ _mm_storeu_ps((float *)(dst + x), dstVector);
}
}
#endif
@@ -544,12 +544,12 @@ void QT_FASTCALL comp_func_Source_rgbafp_avx2(QRgbaFloat32 *dst, const QRgbaFloa
_mm256_storeu_ps((float *)&dst[x], dstVector);
}
if (x < length) {
- __m128 srcVector = _mm_load_ps((const float *)&src[x]);
- __m128 dstVector = _mm_load_ps((const float *)&dst[x]);
+ __m128 srcVector = _mm_loadu_ps((const float *)&src[x]);
+ __m128 dstVector = _mm_loadu_ps((const float *)&dst[x]);
srcVector = _mm_mul_ps(srcVector, constAlphaVector);
dstVector = _mm_mul_ps(dstVector, oneMinusConstAlpha);
dstVector = _mm_add_ps(dstVector, srcVector);
- _mm_store_ps((float *)&dst[x], dstVector);
+ _mm_storeu_ps((float *)&dst[x], dstVector);
}
}
}
@@ -630,7 +630,7 @@ void QT_FASTCALL comp_func_solid_Source_rgbafp_avx2(QRgbaFloat32 *dst, int lengt
const float a = const_alpha / 255.0f;
const __m128 alphaVector = _mm_set1_ps(a);
const __m128 minusAlphaVector = _mm_set1_ps(1.0f - a);
- __m128 colorVector = _mm_load_ps((const float *)&color);
+ __m128 colorVector = _mm_loadu_ps((const float *)&color);
colorVector = _mm_mul_ps(colorVector, alphaVector);
const __m256 colorVector256 = _mm256_insertf128_ps(_mm256_castps128_ps256(colorVector), colorVector, 1);
const __m256 minusAlphaVector256 = _mm256_set1_ps(1.0f - a);
@@ -642,10 +642,10 @@ void QT_FASTCALL comp_func_solid_Source_rgbafp_avx2(QRgbaFloat32 *dst, int lengt
_mm256_storeu_ps((float *)&dst[x], dstVector);
}
if (x < length) {
- __m128 dstVector = _mm_load_ps((const float *)&dst[x]);
+ __m128 dstVector = _mm_loadu_ps((const float *)&dst[x]);
dstVector = _mm_mul_ps(dstVector, minusAlphaVector);
dstVector = _mm_add_ps(dstVector, colorVector);
- _mm_store_ps((float *)&dst[x], dstVector);
+ _mm_storeu_ps((float *)&dst[x], dstVector);
}
}
}
@@ -657,7 +657,7 @@ void QT_FASTCALL comp_func_solid_SourceOver_rgbafp_avx2(QRgbaFloat32 *dst, int l
for (int i = 0; i < length; ++i)
dst[i] = color;
} else {
- __m128 colorVector = _mm_load_ps((const float *)&color);
+ __m128 colorVector = _mm_loadu_ps((const float *)&color);
if (const_alpha != 255)
colorVector = _mm_mul_ps(colorVector, _mm_set1_ps(const_alpha / 255.f));
__m128 minusAlphaOfColorVector =
@@ -673,10 +673,10 @@ void QT_FASTCALL comp_func_solid_SourceOver_rgbafp_avx2(QRgbaFloat32 *dst, int l
_mm256_storeu_ps((float *)&dst[x], dstVector);
}
if (x < length) {
- __m128 dstVector = _mm_load_ps((const float *)&dst[x]);
+ __m128 dstVector = _mm_loadu_ps((const float *)&dst[x]);
dstVector = _mm_mul_ps(dstVector, minusAlphaOfColorVector);
dstVector = _mm_add_ps(dstVector, colorVector);
- _mm_store_ps((float *)&dst[x], dstVector);
+ _mm_storeu_ps((float *)&dst[x], dstVector);
}
}
}
@@ -1345,13 +1345,16 @@ const QRgba64 *QT_FASTCALL fetchRGBA64ToRGBA64PM_avx2(QRgba64 *buffer, const uch
vslo = _mm256_srli_epi32(vslo, 16);
vshi = _mm256_srli_epi32(vshi, 16);
vs256 = _mm256_packus_epi32(vslo, vshi);
+ vs256 = _mm256_blend_epi16(vs256, va256, 0x88);
_mm256_storeu_si256((__m256i *)(buffer + i), vs256);
}
for (; i < count; ++i) {
+ const auto a = s[i].alpha();
__m128i vs = _mm_loadl_epi64((const __m128i *)(s + i));
__m128i va = _mm_shufflelo_epi16(vs, _MM_SHUFFLE(3, 3, 3, 3));
vs = multiplyAlpha65535(vs, va);
_mm_storel_epi64((__m128i *)(buffer + i), vs);
+ buffer[i].setAlpha(a);
}
return buffer;
}
@@ -1554,7 +1557,7 @@ const QRgbaFloat32 *QT_FASTCALL fetchRGBA16FToRGBA32F_avx2(QRgbaFloat32 *buffer,
__m128 vsa = _mm_permute_ps(vsf, _MM_SHUFFLE(3, 3, 3, 3));
vsf = _mm_mul_ps(vsf, vsa);
vsf = _mm_insert_ps(vsf, vsa, 0x30);
- _mm_store_ps((float *)(buffer + i), vsf);
+ _mm_storeu_ps((float *)(buffer + i), vsf);
}
return buffer;
}
@@ -1566,7 +1569,7 @@ void QT_FASTCALL storeRGBX16FFromRGBA32F_avx2(uchar *dest, const QRgbaFloat32 *s
const __m128 *s = reinterpret_cast<const __m128 *>(src);
const __m128 zero = _mm_set_ps(1.0f, 0.0f, 0.0f, 0.0f);
for (int i = 0; i < count; ++i) {
- __m128 vsf = _mm_load_ps(reinterpret_cast<const float *>(s + i));
+ __m128 vsf = _mm_loadu_ps(reinterpret_cast<const float *>(s + i));
const __m128 vsa = _mm_permute_ps(vsf, _MM_SHUFFLE(3, 3, 3, 3));
const float a = _mm_cvtss_f32(vsa);
if (a == 1.0f)
@@ -1590,7 +1593,7 @@ void QT_FASTCALL storeRGBA16FFromRGBA32F_avx2(uchar *dest, const QRgbaFloat32 *s
const __m128 *s = reinterpret_cast<const __m128 *>(src);
const __m128 zero = _mm_set1_ps(0.0f);
for (int i = 0; i < count; ++i) {
- __m128 vsf = _mm_load_ps(reinterpret_cast<const float *>(s + i));
+ __m128 vsf = _mm_loadu_ps(reinterpret_cast<const float *>(s + i));
const __m128 vsa = _mm_permute_ps(vsf, _MM_SHUFFLE(3, 3, 3, 3));
const float a = _mm_cvtss_f32(vsa);
if (a == 1.0f)
diff --git a/src/gui/painting/qdrawhelper_mips_dsp.cpp b/src/gui/painting/qdrawhelper_mips_dsp.cpp
index af2c6598b2..86dcde2b58 100644
--- a/src/gui/painting/qdrawhelper_mips_dsp.cpp
+++ b/src/gui/painting/qdrawhelper_mips_dsp.cpp
@@ -95,7 +95,7 @@ void qt_blend_rgb16_on_rgb16_mips_dspr2(uchar *destPixels, int dbpl,
}
else {
int length = w << 1;
- while (h--) {
+ while (--h >= 0) {
memcpy(destPixels, srcPixels, length);
destPixels += dbpl;
srcPixels += sbpl;
@@ -130,7 +130,7 @@ void qt_blend_rgb16_on_rgb16_mips_dsp(uchar *destPixels, int dbpl,
}
else {
int length = w << 1;
- while (h--) {
+ while (--h >= 0) {
memcpy(destPixels, srcPixels, length);
destPixels += dbpl;
srcPixels += sbpl;
diff --git a/src/gui/painting/qdrawhelper_neon.cpp b/src/gui/painting/qdrawhelper_neon.cpp
index 17ec693649..1fceb83710 100644
--- a/src/gui/painting/qdrawhelper_neon.cpp
+++ b/src/gui/painting/qdrawhelper_neon.cpp
@@ -185,7 +185,7 @@ void qt_blend_rgb16_on_argb32_neon(uchar *destPixels, int dbpl,
quint8 a = (255 * const_alpha) >> 8;
quint8 ia = 255 - a;
- while (h--) {
+ while (--h >= 0) {
for (int x=0; x<w; ++x)
dst[x] = INTERPOLATE_PIXEL_255(qConvertRgb16To32(src[x]), a, dst[x], ia);
dst += dbpl;
@@ -228,7 +228,7 @@ static inline void blockBlit16(quint16 *dst, quint16 *src, int dstride, int sstr
u.pointer = dst;
if (u.address & 2) {
- while (h--) {
+ while (--h >= 0) {
// align dst
dst[0] = src[0];
if (Width > 1)
@@ -237,7 +237,7 @@ static inline void blockBlit16(quint16 *dst, quint16 *src, int dstride, int sstr
src += sstride;
}
} else {
- while (h--) {
+ while (--h >= 0) {
scanLineBlit16<Width>(dst, src, dstride);
dst += dstride;
diff --git a/src/gui/painting/qdrawhelper_p.h b/src/gui/painting/qdrawhelper_p.h
index 5f58d55dd0..833ddd7b16 100644
--- a/src/gui/painting/qdrawhelper_p.h
+++ b/src/gui/painting/qdrawhelper_p.h
@@ -174,7 +174,6 @@ struct RadialGradientValues
qreal dr;
qreal sqrfr;
qreal a;
- qreal inv2a;
bool extended;
};
@@ -402,12 +401,12 @@ const BlendType * QT_FASTCALL qt_fetch_radial_gradient_template(BlendType *buffe
bool affine = !data->m13 && !data->m23;
BlendType *end = buffer + length;
+ qreal inv_a = 1 / qreal(2 * op->radial.a);
+
if (affine) {
rx -= data->gradient.radial.focal.x;
ry -= data->gradient.radial.focal.y;
- qreal inv_a = 1 / qreal(2 * op->radial.a);
-
const qreal delta_rx = data->m11;
const qreal delta_ry = data->m12;
@@ -452,8 +451,8 @@ const BlendType * QT_FASTCALL qt_fetch_radial_gradient_template(BlendType *buffe
if (det >= 0) {
qreal detSqrt = qSqrt(det);
- qreal s0 = (-b - detSqrt) * op->radial.inv2a;
- qreal s1 = (-b + detSqrt) * op->radial.inv2a;
+ qreal s0 = (-b - detSqrt) * inv_a;
+ qreal s1 = (-b + detSqrt) * inv_a;
qreal s = qMax(s0, s1);
diff --git a/src/gui/painting/qdrawhelper_sse2.cpp b/src/gui/painting/qdrawhelper_sse2.cpp
index bc9286ab38..79590534f3 100644
--- a/src/gui/painting/qdrawhelper_sse2.cpp
+++ b/src/gui/painting/qdrawhelper_sse2.cpp
@@ -361,7 +361,7 @@ void qt_bitmapblit32_sse2_base(QRasterBuffer *rasterBuffer, int x, int y,
0x04040404, 0x08080808);
const __m128i maskadd2 = _mm_set_epi32(0x7f7f7f7f, 0x7e7e7e7e,
0x7c7c7c7c, 0x78787878);
- while (height--) {
+ while (--height >= 0) {
for (int x = 0; x < width; x += 8) {
const quint8 s = src[x >> 3];
if (!s)
@@ -380,7 +380,7 @@ void qt_bitmapblit32_sse2_base(QRasterBuffer *rasterBuffer, int x, int y,
src += stride;
}
} else {
- while (height--) {
+ while (--height >= 0) {
const quint8 s = *src;
if (s) {
__m128i mask1 = _mm_set1_epi8(s);
@@ -423,7 +423,7 @@ QT_WARNING_DISABLE_MSVC(4309) // truncation of constant value
const __m128i maskadd = _mm_set_epi16(0x7f7f, 0x7e7e, 0x7c7c, 0x7878,
0x7070, 0x6060, 0x4040, 0x0000);
- while (height--) {
+ while (--height >= 0) {
for (int x = 0; x < width; x += 8) {
const quint8 s = src[x >> 3];
if (!s)
@@ -558,7 +558,7 @@ void qt_scale_image_argb32_on_argb32_sse2(uchar *destPixels, int dbpl,
if (xend < 0 || xend >= (int)(sbpl/sizeof(quint32)))
--w;
- while (h--) {
+ while (--h >= 0) {
const uint *src = (const quint32 *) (srcPixels + (srcy >> 16) * sbpl);
int srcx = basex;
int x = 0;
diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp
index 9d95c80bf8..a2786fbb8b 100644
--- a/src/gui/painting/qicc.cpp
+++ b/src/gui/painting/qicc.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2020 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qicc_p.h"
@@ -12,6 +12,8 @@
#include <qloggingcategory.h>
#include <qstring.h>
+#include "qcolorclut_p.h"
+#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
@@ -20,6 +22,8 @@
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc", QtWarningMsg)
+namespace QIcc {
+
struct ICCProfileHeader
{
quint32_be profileSize;
@@ -58,18 +62,23 @@ constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
enum class ColorSpaceType : quint32 {
Rgb = IccTag('R', 'G', 'B', ' '),
Gray = IccTag('G', 'R', 'A', 'Y'),
+ Cmyk = IccTag('C', 'M', 'Y', 'K'),
};
enum class ProfileClass : quint32 {
- Input = IccTag('s', 'c', 'r', 'n'),
+ Input = IccTag('s', 'c', 'n', 'r'),
Display = IccTag('m', 'n', 't', 'r'),
- // Not supported:
Output = IccTag('p', 'r', 't', 'r'),
ColorSpace = IccTag('s', 'p', 'a', 'c'),
+ // Not supported:
+ DeviceLink = IccTag('l', 'i', 'n', 'k'),
+ Abstract = IccTag('a', 'b', 's', 't'),
+ NamedColor = IccTag('n', 'm', 'c', 'l'),
};
enum class Tag : quint32 {
acsp = IccTag('a', 'c', 's', 'p'),
+ Lab_ = IccTag('L', 'a', 'b', ' '),
RGB_ = IccTag('R', 'G', 'B', ' '),
XYZ_ = IccTag('X', 'Y', 'Z', ' '),
rXYZ = IccTag('r', 'X', 'Y', 'Z'),
@@ -81,8 +90,18 @@ enum class Tag : quint32 {
kTRC = IccTag('k', 'T', 'R', 'C'),
A2B0 = IccTag('A', '2', 'B', '0'),
A2B1 = IccTag('A', '2', 'B', '1'),
+ A2B2 = IccTag('A', '2', 'B', '2'),
B2A0 = IccTag('B', '2', 'A', '0'),
B2A1 = IccTag('B', '2', 'A', '1'),
+ B2A2 = IccTag('B', '2', 'A', '2'),
+ B2D0 = IccTag('B', '2', 'D', '0'),
+ B2D1 = IccTag('B', '2', 'D', '1'),
+ B2D2 = IccTag('B', '2', 'D', '2'),
+ B2D3 = IccTag('B', '2', 'D', '3'),
+ D2B0 = IccTag('D', '2', 'B', '0'),
+ D2B1 = IccTag('D', '2', 'B', '1'),
+ D2B2 = IccTag('D', '2', 'B', '2'),
+ D2B3 = IccTag('D', '2', 'B', '3'),
desc = IccTag('d', 'e', 's', 'c'),
text = IccTag('t', 'e', 'x', 't'),
cprt = IccTag('c', 'p', 'r', 't'),
@@ -93,9 +112,11 @@ enum class Tag : quint32 {
mft1 = IccTag('m', 'f', 't', '1'),
mft2 = IccTag('m', 'f', 't', '2'),
mluc = IccTag('m', 'l', 'u', 'c'),
+ mpet = IccTag('m', 'p', 'e', 't'),
mAB_ = IccTag('m', 'A', 'B', ' '),
mBA_ = IccTag('m', 'B', 'A', ' '),
chad = IccTag('c', 'h', 'a', 'd'),
+ gamt = IccTag('g', 'a', 'm', 't'),
sf32 = IccTag('s', 'f', '3', '2'),
// Apple extensions for ICCv2:
@@ -104,7 +125,9 @@ enum class Tag : quint32 {
aabg = IccTag('a', 'a', 'b', 'g'),
};
-inline size_t qHash(const Tag &key, size_t seed = 0)
+} // namespace QIcc
+
+inline size_t qHash(const QIcc::Tag &key, size_t seed = 0)
{
return qHash(quint32(key), seed);
}
@@ -159,6 +182,46 @@ struct MlucTagData : GenericTagData {
MlucTagRecord records[1];
};
+struct Lut8TagData : GenericTagData {
+ quint8 inputChannels;
+ quint8 outputChannels;
+ quint8 clutGridPoints;
+ quint8 padding;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ // followed by parameter values: quint8[inputChannels * 256];
+ // followed by parameter values: quint8[outputChannels * clutGridPoints^inputChannels];
+ // followed by parameter values: quint8[outputChannels * 256];
+};
+
+struct Lut16TagData : GenericTagData {
+ quint8 inputChannels;
+ quint8 outputChannels;
+ quint8 clutGridPoints;
+ quint8 padding;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ quint16_be inputTableEntries;
+ quint16_be outputTableEntries;
+ // followed by parameter values: quint16_be[inputChannels * inputTableEntries];
+ // followed by parameter values: quint16_be[outputChannels * clutGridPoints^inputChannels];
+ // followed by parameter values: quint16_be[outputChannels * outputTableEntries];
+};
+
// For both mAB and mBA
struct mABTagData : GenericTagData {
quint8 inputChannels;
@@ -169,10 +232,34 @@ struct mABTagData : GenericTagData {
quint32_be mCurvesOffset;
quint32_be clutOffset;
quint32_be aCurvesOffset;
+ // followed by embedded data for the offsets above
+};
+
+struct mpetTagData : GenericTagData {
+ quint16_be inputChannels;
+ quint16_be outputChannels;
+ quint32_be processingElements;
+ // element offset table
+ // element data
};
struct Sf32TagData : GenericTagData {
- quint32_be value[1];
+ quint32_be value[9];
+};
+
+struct MatrixElement {
+ qint32_be e0;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ qint32_be e10;
+ qint32_be e11;
};
static int toFixedS1516(float x)
@@ -204,19 +291,19 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
if (header.profileClass != uint(ProfileClass::Input)
&& header.profileClass != uint(ProfileClass::Display)
- && (header.profileClass != uint(ProfileClass::Output)
- || header.inputColorSpace != uint(ColorSpaceType::Gray))) {
+ && header.profileClass != uint(ProfileClass::Output)
+ && header.profileClass != uint(ProfileClass::ColorSpace)) {
qCInfo(lcIcc, "Unsupported ICC profile class 0x%x", quint32(header.profileClass));
return false;
}
if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
- && header.inputColorSpace != uint(ColorSpaceType::Gray)) {
+ && header.inputColorSpace != uint(ColorSpaceType::Gray)
+ && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) {
qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
return false;
}
- if (header.pcs != 0x58595a20 /* 'XYZ '*/) {
- // ### support PCSLAB
- qCInfo(lcIcc, "Unsupported ICC profile connection space 0x%x", quint32(header.pcs));
+ if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) {
+ qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs));
return false;
}
@@ -234,7 +321,7 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
{
- if (trc.isLinear()) {
+ if (trc.isIdentity()) {
stream << uint(Tag::curv) << uint(0);
stream << uint(0);
return 12;
@@ -274,6 +361,10 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
stream << ushort(trc.m_table.m_table8[i] * 257U);
}
}
+ if (trc.m_table.m_tableSize & 1) {
+ stream << ushort(0);
+ return 12 + 2 * trc.m_table.m_tableSize + 2;
+ }
return 12 + 2 * trc.m_table.m_tableSize;
}
@@ -283,10 +374,21 @@ QByteArray toIccProfile(const QColorSpace &space)
return QByteArray();
const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
+ // This should catch anything not three component matrix based as we can only get that from parsed ICC
+ if (!spaceDPtr->iccProfile.isEmpty())
+ return spaceDPtr->iccProfile;
+ Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
+
+ int fixedLengthTagCount = 5;
+ bool writeChad = false;
+ if (!spaceDPtr->whitePoint.isNull() && spaceDPtr->whitePoint != QColorVector::D50()) {
+ writeChad = true;
+ fixedLengthTagCount++;
+ }
- constexpr int tagCount = 9;
- constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
- constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
+ const int tagCount = fixedLengthTagCount + 4;
+ const uint profileDataOffset = 128 + 4 + 12 * tagCount;
+ const uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
uint currentOffset = 0;
uint rTrcOffset, gTrcOffset, bTrcOffset;
uint rTrcSize, gTrcSize, bTrcSize;
@@ -299,10 +401,10 @@ QByteArray toIccProfile(const QColorSpace &space)
// Profile header:
stream << uint(0); // Size, we will update this later
stream << uint(0);
- stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
+ stream << uint(0x04400000); // Version 4.4
stream << uint(ProfileClass::Display);
stream << uint(Tag::RGB_);
- stream << uint(Tag::XYZ_);
+ stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
@@ -316,19 +418,23 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
// Tag table:
+ currentOffset = profileDataOffset;
stream << uint(tagCount);
stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
- stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
+ stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(34);
+ currentOffset += 20 + 20 + 20 + 20 + 34 + 2;
+ if (writeChad) {
+ stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
+ currentOffset += 44;
+ }
// From here the offset and size will be updated later:
stream << uint(Tag::rTRC) << uint(0) << uint(0);
stream << uint(Tag::gTRC) << uint(0) << uint(0);
stream << uint(Tag::bTRC) << uint(0) << uint(0);
stream << uint(Tag::desc) << uint(0) << uint(0);
- // TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
- currentOffset = profileDataOffset;
// Tag data:
stream << uint(Tag::XYZ_) << uint(0);
@@ -347,9 +453,25 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << toFixedS1516(spaceDPtr->whitePoint.x);
stream << toFixedS1516(spaceDPtr->whitePoint.y);
stream << toFixedS1516(spaceDPtr->whitePoint.z);
- stream << uint(Tag::text) << uint(0);
- stream << uint(IccTag('N', '/', 'A', '\0'));
- currentOffset += 92;
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+ if (writeChad) {
+ QColorMatrix chad = QColorMatrix::chromaticAdaptation(spaceDPtr->whitePoint);
+ stream << uint(Tag::sf32) << uint(0);
+ stream << toFixedS1516(chad.r.x);
+ stream << toFixedS1516(chad.g.x);
+ stream << toFixedS1516(chad.b.x);
+ stream << toFixedS1516(chad.r.y);
+ stream << toFixedS1516(chad.g.y);
+ stream << toFixedS1516(chad.b.y);
+ stream << toFixedS1516(chad.r.z);
+ stream << toFixedS1516(chad.g.z);
+ stream << toFixedS1516(chad.b.z);
+ }
// From now on the data is variable sized:
rTrcOffset = currentOffset;
@@ -372,16 +494,20 @@ QByteArray toIccProfile(const QColorSpace &space)
currentOffset += bTrcSize;
}
+ // Writing description
descOffset = currentOffset;
- QByteArray description = space.description().toUtf8();
- stream << uint(Tag::desc) << uint(0);
- stream << uint(description.size() + 1);
- stream.writeRawData(description.constData(), description.size() + 1);
- stream << uint(0) << uint(0);
- stream << ushort(0) << uchar(0);
- QByteArray macdesc(67, '\0');
- stream.writeRawData(macdesc.constData(), 67);
- descSize = 90 + description.size() + 1;
+ const QString description = space.description();
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(description.size() * 2) << uint(28);
+ for (QChar ch : description)
+ stream << ushort(ch.unicode());
+ descSize = 28 + description.size() * 2;
+ if (description.size() & 1) {
+ stream << ushort(0);
+ currentOffset += 2;
+ }
currentOffset += descSize;
buffer.close();
@@ -412,7 +538,7 @@ struct TagEntry {
quint32 size;
};
-bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
+static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
{
if (tagEntry.size < sizeof(XYZTagData)) {
qCWarning(lcIcc) << "Undersized XYZ tag";
@@ -431,23 +557,24 @@ bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector
return true;
}
-bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma)
+static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type = QColorTransferTable::TwoWay)
{
- const GenericTagData trcData = qFromUnaligned<GenericTagData>(data.constData()
- + tagEntry.offset);
+ if (tagData.size() < 12)
+ return 0;
+ const GenericTagData trcData = qFromUnaligned<GenericTagData>(tagData.constData());
if (trcData.type == quint32(Tag::curv)) {
Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
- const CurvTagData curv = qFromUnaligned<CurvTagData>(data.constData() + tagEntry.offset);
+ const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
if (curv.valueCount > (1 << 16))
- return false;
- if (tagEntry.size - 12 < 2 * curv.valueCount)
- return false;
- const auto valueOffset = tagEntry.offset + sizeof(CurvTagData);
+ return 0;
+ if (tagData.size() < qsizetype(12 + 2 * curv.valueCount))
+ return 0;
+ const auto valueOffset = sizeof(CurvTagData);
if (curv.valueCount == 0) {
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(); // Linear
} else if (curv.valueCount == 1) {
- const quint16 v = qFromBigEndian<quint16>(data.constData() + valueOffset);
+ const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
} else {
@@ -455,12 +582,12 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
tabl.resize(curv.valueCount);
static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
"GenericTagData has padding. The following code is a subject to UB.");
- qFromBigEndian<quint16>(data.constData() + valueOffset, curv.valueCount, tabl.data());
- QColorTransferTable table = QColorTransferTable(curv.valueCount, std::move(tabl));
+ qFromBigEndian<quint16>(tagData.constData() + valueOffset, curv.valueCount, tabl.data());
+ QColorTransferTable table(curv.valueCount, tabl, type);
QColorTransferFunction curve;
if (!table.checkValidity()) {
qCWarning(lcIcc) << "Invalid curv table";
- return false;
+ return 0;
} else if (!table.asColorTransferFunction(&curve)) {
gamma.m_type = QColorTrc::Type::Table;
gamma.m_table = table;
@@ -470,43 +597,43 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
gamma.m_fun = curve;
}
}
- return true;
+ return 12 + 2 * curv.valueCount;
}
if (trcData.type == quint32(Tag::para)) {
Q_STATIC_ASSERT(sizeof(ParaTagData) == 12);
- const ParaTagData para = qFromUnaligned<ParaTagData>(data.constData() + tagEntry.offset);
- const auto parametersOffset = tagEntry.offset + sizeof(ParaTagData);
+ const ParaTagData para = qFromUnaligned<ParaTagData>(tagData.constData());
+ const auto parametersOffset = sizeof(ParaTagData);
quint32 parameters[7];
switch (para.curveType) {
case 0: {
- if (tagEntry.size < sizeof(ParaTagData) + 1 * 4)
- return false;
- qFromBigEndian<quint32>(data.constData() + parametersOffset, 1, parameters);
+ if (tagData.size() < 12 + 1 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
float g = fromFixedS1516(parameters[0]);
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
- break;
+ return 12 + 1 * 4;
}
case 1: {
- if (tagEntry.size < sizeof(ParaTagData) + 3 * 4)
- return false;
- qFromBigEndian<quint32>(data.constData() + parametersOffset, 3, parameters);
+ if (tagData.size() < 12 + 3 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 3, parameters);
if (parameters[1] == 0)
- return false;
+ return 0;
float g = fromFixedS1516(parameters[0]);
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
float d = -b / a;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
- break;
+ return 12 + 3 * 4;
}
case 2: {
- if (tagEntry.size < sizeof(ParaTagData) + 4 * 4)
- return false;
- qFromBigEndian<quint32>(data.constData() + parametersOffset, 4, parameters);
+ if (tagData.size() < 12 + 4 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 4, parameters);
if (parameters[1] == 0)
- return false;
+ return 0;
float g = fromFixedS1516(parameters[0]);
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
@@ -514,12 +641,12 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
float d = -b / a;
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
- break;
+ return 12 + 4 * 4;
}
case 3: {
- if (tagEntry.size < sizeof(ParaTagData) + 5 * 4)
- return false;
- qFromBigEndian<quint32>(data.constData() + parametersOffset, 5, parameters);
+ if (tagData.size() < 12 + 5 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 5, parameters);
float g = fromFixedS1516(parameters[0]);
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
@@ -527,12 +654,12 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
float d = fromFixedS1516(parameters[4]);
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
- break;
+ return 12 + 5 * 4;
}
case 4: {
- if (tagEntry.size < sizeof(ParaTagData) + 7 * 4)
- return false;
- qFromBigEndian<quint32>(data.constData() + parametersOffset, 7, parameters);
+ if (tagData.size() < 12 + 7 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 7, parameters);
float g = fromFixedS1516(parameters[0]);
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
@@ -542,24 +669,408 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
float f = fromFixedS1516(parameters[6]);
gamma.m_type = QColorTrc::Type::Function;
gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
- break;
+ return 12 + 7 * 4;
}
default:
qCWarning(lcIcc) << "Unknown para type" << uint(para.curveType);
+ return 0;
+ }
+ return true;
+ }
+ qCWarning(lcIcc) << "Invalid TRC data type" << Qt::hex << trcData.type;
+ return 0;
+}
+
+template<typename T>
+static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
+{
+ if (outputChannels == 4) {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 4 + 0] * f,
+ tableData[index * 4 + 1] * f,
+ tableData[index * 4 + 2] * f,
+ tableData[index * 4 + 3] * f);
+ clut->table[index] = v;
+ };
+ } else {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 3 + 0] * f,
+ tableData[index * 3 + 1] * f,
+ tableData[index * 3 + 2] * f);
+ clut->table[index] = v;
+ };
+ }
+}
+
+// very simple version for small values (<=4) of exp.
+static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
+{
+ return (exp <= 1) ? x : x * intPow(x, exp - 1);
+}
+
+// Parses lut8 and lut16 type elements
+template<typename T>
+static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
+{
+ if (tagEntry.size < sizeof(T)) {
+ qCWarning(lcIcc) << "Undersized lut8/lut16 tag";
+ return false;
+ }
+ if (qsizetype(tagEntry.size) > data.size()) {
+ qCWarning(lcIcc) << "Truncated lut8/lut16 tag";
+ return false;
+ }
+ using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
+ const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
+ int inputTableEntries, outputTableEntries, precision;
+ if constexpr (std::is_same_v<T, Lut8TagData>) {
+ Q_ASSERT(lut.type == quint32(Tag::mft1));
+ if (!colorSpacePrivate->isPcsLab && isAb) {
+ qCWarning(lcIcc) << "Lut8 can not output XYZ values";
+ return false;
+ }
+ inputTableEntries = 256;
+ outputTableEntries = 256;
+ precision = 1;
+ } else {
+ Q_ASSERT(lut.type == quint32(Tag::mft2));
+ inputTableEntries = lut.inputTableEntries;
+ outputTableEntries = lut.outputTableEntries;
+ if (inputTableEntries < 2 || inputTableEntries > 4096)
+ return false;
+ if (outputTableEntries < 2 || outputTableEntries > 4096)
+ return false;
+ precision = 2;
+ }
+
+ bool inTableIsLinear = true, outTableIsLinear = true;
+ QColorSpacePrivate::TransferElement inTableElement;
+ QColorSpacePrivate::TransferElement outTableElement;
+ QColorCLUT clutElement;
+ QColorMatrix matrixElement;
+
+ matrixElement.r.x = fromFixedS1516(lut.e1);
+ matrixElement.g.x = fromFixedS1516(lut.e2);
+ matrixElement.b.x = fromFixedS1516(lut.e3);
+ matrixElement.r.y = fromFixedS1516(lut.e4);
+ matrixElement.g.y = fromFixedS1516(lut.e5);
+ matrixElement.b.y = fromFixedS1516(lut.e6);
+ matrixElement.r.z = fromFixedS1516(lut.e7);
+ matrixElement.g.z = fromFixedS1516(lut.e8);
+ matrixElement.b.z = fromFixedS1516(lut.e9);
+ if (!colorSpacePrivate->isPcsLab && !isAb && !matrixElement.isValid()) {
+ qCWarning(lcIcc) << "Invalid matrix values in lut8/lut16";
+ return false;
+ }
+
+ if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) {
+ qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
+ return false;
+ }
+
+ if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) {
+ qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
+ return false;
+ }
+
+ const qsizetype clutTableSize = intPow(lut.clutGridPoints, lut.inputChannels);
+ if (tagEntry.size < (sizeof(T) + precision * lut.inputChannels * inputTableEntries
+ + precision * lut.outputChannels * outputTableEntries
+ + precision * lut.outputChannels * clutTableSize)) {
+ qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
+ return false;
+ }
+ if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
+ return false;
+ }
+
+ const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
+
+ for (int j = 0; j < lut.inputChannels; ++j) {
+ QList<S> input(inputTableEntries);
+ qFromBigEndian<S>(tableData, inputTableEntries, input.data());
+ QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
+ if (!table.checkValidity()) {
+ qCWarning(lcIcc) << "Bad input table in lut8/lut16";
+ return false;
+ }
+ if (!table.isIdentity())
+ inTableIsLinear = false;
+ inTableElement.trc[j] = std::move(table);
+ tableData += inputTableEntries * precision;
+ }
+
+ clutElement.table.resize(clutTableSize);
+ clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
+ if (lut.inputChannels == 4)
+ clutElement.gridPointsW = lut.clutGridPoints;
+
+ if constexpr (std::is_same_v<T, Lut8TagData>) {
+ parseCLUT(tableData, 1.f / 255.f, &clutElement, lut.outputChannels);
+ } else {
+ float f = 1.0f / 65535.f;
+ if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
+ f = 1.0f / 65280.f;
+ QList<S> clutTable(clutTableSize * lut.outputChannels);
+ qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
+ parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
+ }
+ tableData += clutTableSize * lut.outputChannels * precision;
+
+ for (int j = 0; j < lut.outputChannels; ++j) {
+ QList<S> output(outputTableEntries);
+ qFromBigEndian<S>(tableData, outputTableEntries, output.data());
+ QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
+ if (!table.checkValidity()) {
+ qCWarning(lcIcc) << "Bad output table in lut8/lut16";
return false;
}
+ if (!table.isIdentity())
+ outTableIsLinear = false;
+ outTableElement.trc[j] = std::move(table);
+ tableData += outputTableEntries * precision;
+ }
+
+ if (isAb) {
+ if (!inTableIsLinear)
+ colorSpacePrivate->mAB.append(inTableElement);
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mAB.append(clutElement);
+ if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
+ colorSpacePrivate->mAB.append(outTableElement);
+ } else {
+ // The matrix is only to be applied if the input color-space is XYZ
+ if (!colorSpacePrivate->isPcsLab && !matrixElement.isIdentity())
+ colorSpacePrivate->mBA.append(matrixElement);
+ if (!inTableIsLinear)
+ colorSpacePrivate->mBA.append(inTableElement);
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mBA.append(clutElement);
+ if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
+ colorSpacePrivate->mBA.append(outTableElement);
+ }
+ return true;
+}
+
+// Parses mAB and mBA type elements
+static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
+{
+ if (tagEntry.size < sizeof(mABTagData)) {
+ qCWarning(lcIcc) << "Undersized mAB/mBA tag";
+ return false;
+ }
+ if (qsizetype(tagEntry.size) > data.size()) {
+ qCWarning(lcIcc) << "Truncated mAB/mBA tag";
+ return false;
+ }
+ const mABTagData mab = qFromUnaligned<mABTagData>(data.constData() + tagEntry.offset);
+ if ((mab.type != quint32(Tag::mAB_) && isAb) || (mab.type != quint32(Tag::mBA_) && !isAb)){
+ qCWarning(lcIcc) << "Bad mAB/mBA content type";
+ return false;
+ }
+
+ if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) {
+ qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
+ return false;
+ }
+
+ if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) {
+ qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
+ return false;
+ }
+
+ // These combinations are legal: B, M + Matrix + B, A + Clut + B, A + Clut + M + Matrix + B
+ if (!mab.bCurvesOffset) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA without B table";
+ return false;
+ }
+ if (((bool)mab.matrixOffset != (bool)mab.mCurvesOffset) ||
+ ((bool)mab.aCurvesOffset != (bool)mab.clutOffset)) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA element combination";
+ return false;
+ }
+
+ if (mab.aCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.bCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.mCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.matrixOffset > (tagEntry.size - 4 * 12) ||
+ mab.clutOffset > (tagEntry.size - 20)) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA element offset";
+ return false;
+ }
+
+ QColorSpacePrivate::TransferElement bTableElement;
+ QColorSpacePrivate::TransferElement aTableElement;
+ QColorCLUT clutElement;
+ QColorSpacePrivate::TransferElement mTableElement;
+ QColorMatrix matrixElement;
+ QColorVector offsetElement;
+
+ auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table, int channels) {
+ for (int i = 0; i < channels; ++i) {
+ if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
+ qCWarning(lcIcc) << "Space missing for channel curves in mAB/mBA";
+ return false;
+ }
+ auto size = parseTRC(QByteArrayView(data).sliced(tagEntry.offset + curvesOffset, tagEntry.size - curvesOffset), table[i], QColorTransferTable::OneWay);
+ if (!size)
+ return false;
+ if (size & 2) size += 2; // possible padding
+ curvesOffset += size;
+ }
return true;
+ };
+
+ bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
+
+ // B Curves
+ if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? mab.outputChannels : mab.inputChannels)) {
+ qCWarning(lcIcc) << "Invalid B curves";
+ return false;
+ } else {
+ bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
}
- qCWarning(lcIcc) << "Invalid TRC data type";
+
+ // A Curves
+ if (mab.aCurvesOffset) {
+ if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? mab.inputChannels : mab.outputChannels)) {
+ qCWarning(lcIcc) << "Invalid A curves";
+ return false;
+ } else {
+ aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
+ }
+ }
+
+ // M Curves
+ if (mab.mCurvesOffset) {
+ if (!parseCurves(mab.mCurvesOffset, mTableElement.trc, 3)) {
+ qCWarning(lcIcc) << "Invalid M curves";
+ return false;
+ } else {
+ mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
+ }
+ }
+
+ // Matrix
+ if (mab.matrixOffset) {
+ const MatrixElement matrix = qFromUnaligned<MatrixElement>(data.constData() + tagEntry.offset + mab.matrixOffset);
+ matrixElement.r.x = fromFixedS1516(matrix.e0);
+ matrixElement.g.x = fromFixedS1516(matrix.e1);
+ matrixElement.b.x = fromFixedS1516(matrix.e2);
+ matrixElement.r.y = fromFixedS1516(matrix.e3);
+ matrixElement.g.y = fromFixedS1516(matrix.e4);
+ matrixElement.b.y = fromFixedS1516(matrix.e5);
+ matrixElement.r.z = fromFixedS1516(matrix.e6);
+ matrixElement.g.z = fromFixedS1516(matrix.e7);
+ matrixElement.b.z = fromFixedS1516(matrix.e8);
+ offsetElement.x = fromFixedS1516(matrix.e9);
+ offsetElement.y = fromFixedS1516(matrix.e10);
+ offsetElement.z = fromFixedS1516(matrix.e11);
+ if (!matrixElement.isValid() || !offsetElement.isValid()) {
+ qCWarning(lcIcc) << "Invalid matrix values in mAB/mBA element";
+ return false;
+ }
+ }
+
+ // CLUT
+ if (mab.clutOffset) {
+ clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
+ clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
+ clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]);
+ clutElement.gridPointsW = std::max(uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), uint8_t(1));
+ const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
+ if (precision > 2 || precision < 1) {
+ qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision";
+ return false;
+ }
+ if (clutElement.gridPointsX < 2 || clutElement.gridPointsY < 2 || clutElement.gridPointsZ < 2) {
+ qCWarning(lcIcc) << "Empty CLUT";
+ return false;
+ }
+ const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
+ if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
+ qCWarning(lcIcc) << "CLUT oversized for tag";
+ return false;
+ }
+
+ clutElement.table.resize(clutTableSize);
+ if (precision == 2) {
+ QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
+ qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
+ parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, mab.outputChannels);
+ } else {
+ const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
+ parseCLUT(clutTable, (1.f/255.f), &clutElement, mab.outputChannels);
+ }
+ } else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
+ return false;
+ }
+
+ if (isAb) {
+ if (mab.aCurvesOffset) {
+ if (!aCurvesAreLinear)
+ colorSpacePrivate->mAB.append(std::move(aTableElement));
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mAB.append(std::move(clutElement));
+ }
+ if (mab.mCurvesOffset && mab.outputChannels == 3) {
+ if (!mCurvesAreLinear)
+ colorSpacePrivate->mAB.append(std::move(mTableElement));
+ if (!matrixElement.isIdentity())
+ colorSpacePrivate->mAB.append(std::move(matrixElement));
+ if (!offsetElement.isNull())
+ colorSpacePrivate->mAB.append(std::move(offsetElement));
+ }
+ if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
+ colorSpacePrivate->mAB.append(std::move(bTableElement));
+ } else {
+ if (!bCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(bTableElement));
+ if (mab.mCurvesOffset && mab.inputChannels == 3) {
+ if (!matrixElement.isIdentity())
+ colorSpacePrivate->mBA.append(std::move(matrixElement));
+ if (!offsetElement.isNull())
+ colorSpacePrivate->mBA.append(std::move(offsetElement));
+ if (!mCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(mTableElement));
+ }
+ if (mab.aCurvesOffset) {
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mBA.append(std::move(clutElement));
+ if (!aCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(aTableElement));
+ }
+ if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
+ colorSpacePrivate->mBA.append(std::move(bTableElement));
+ }
+
+ return true;
+}
+
+static bool parseA2B(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *privat, bool isAb)
+{
+ const GenericTagData a2bData = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
+ if (a2bData.type == quint32(Tag::mft1))
+ return parseLutData<Lut8TagData>(data, tagEntry, privat, isAb);
+ else if (a2bData.type == quint32(Tag::mft2))
+ return parseLutData<Lut16TagData>(data, tagEntry, privat, isAb);
+ else if (a2bData.type == quint32(Tag::mAB_) || a2bData.type == quint32(Tag::mBA_))
+ return parseMabData(data, tagEntry, privat, isAb);
+
+ qCWarning(lcIcc) << "fromIccProfile: Unknown A2B/B2A data type";
return false;
}
-bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
+static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
{
const GenericTagData tag = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
// Either 'desc' (ICCv2) or 'mluc' (ICCv4)
if (tag.type == quint32(Tag::desc)) {
+ if (tagEntry.size < sizeof(DescTagData))
+ return false;
Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
const quint32 len = desc.asciiDescriptionLength;
@@ -581,7 +1092,7 @@ bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descNa
const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
if (mluc.recordCount < 1)
return false;
- if (mluc.recordSize < 12)
+ if (mluc.recordSize != 12)
return false;
// We just use the primary record regardless of language or country.
const quint32 stringOffset = mluc.records[0].offset;
@@ -601,6 +1112,147 @@ bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descNa
return true;
}
+static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
+{
+ // Parse XYZ tags
+ if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
+ return false;
+ if (!colorspaceDPtr->toXyz.isValid() || !colorspaceDPtr->whitePoint.isValid() || colorspaceDPtr->whitePoint.isNull()) {
+ qCWarning(lcIcc) << "Invalid XYZ values in RGB matrix";
+ return false;
+ }
+
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
+ if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
+ qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
+ }
+ if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
+ }
+ return true;
+}
+
+static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
+{
+ QColorVector whitePoint;
+ if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
+ return false;
+ if (!whitePoint.isValid() || !qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
+ return false;
+ }
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
+ colorspaceDPtr->whitePoint = whitePoint;
+ return true;
+}
+
+static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
+{
+ if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
+ return false;
+ const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(data.constData() + tagEntry.offset);
+ if (chadtag.type != uint32_t(Tag::sf32)) {
+ qCWarning(lcIcc, "fromIccProfile: bad chad data type");
+ return false;
+ }
+ QColorMatrix chad;
+ chad.r.x = fromFixedS1516(chadtag.value[0]);
+ chad.g.x = fromFixedS1516(chadtag.value[1]);
+ chad.b.x = fromFixedS1516(chadtag.value[2]);
+ chad.r.y = fromFixedS1516(chadtag.value[3]);
+ chad.g.y = fromFixedS1516(chadtag.value[4]);
+ chad.b.y = fromFixedS1516(chadtag.value[5]);
+ chad.r.z = fromFixedS1516(chadtag.value[6]);
+ chad.g.z = fromFixedS1516(chadtag.value[7]);
+ chad.b.z = fromFixedS1516(chadtag.value[8]);
+
+ if (!chad.isValid()) {
+ qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
+ return false;
+ }
+ colorspaceDPtr->chad = chad;
+ return true;
+}
+
+static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
+{
+ TagEntry rTrc;
+ TagEntry gTrc;
+ TagEntry bTrc;
+ if (isColorSpaceTypeGray) {
+ rTrc = tagIndex[Tag::kTRC];
+ gTrc = tagIndex[Tag::kTRC];
+ bTrc = tagIndex[Tag::kTRC];
+ } else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
+ // Apple extension for parametric version of TRCs in ICCv2:
+ rTrc = tagIndex[Tag::aarg];
+ gTrc = tagIndex[Tag::aagg];
+ bTrc = tagIndex[Tag::aabg];
+ } else {
+ rTrc = tagIndex[Tag::rTRC];
+ gTrc = tagIndex[Tag::gTRC];
+ bTrc = tagIndex[Tag::bTRC];
+ }
+
+ QColorTrc rCurve;
+ QColorTrc gCurve;
+ QColorTrc bCurve;
+ if (!parseTRC(QByteArrayView(data).sliced(rTrc.offset, rTrc.size), rCurve, QColorTransferTable::TwoWay)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
+ return false;
+ }
+ if (!parseTRC(QByteArrayView(data).sliced(gTrc.offset, gTrc.size), gCurve, QColorTransferTable::TwoWay)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
+ return false;
+ }
+ if (!parseTRC(QByteArrayView(data).sliced(bTrc.offset, bTrc.size), bCurve, QColorTransferTable::TwoWay)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
+ return false;
+ }
+ if (rCurve == gCurve && gCurve == bCurve) {
+ if (rCurve.isIdentity()) {
+ qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
+ colorspaceDPtr->trc[0] = QColorTransferFunction();
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
+ colorspaceDPtr->gamma = 1.0f;
+ } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isGamma()) {
+ qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
+ colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
+ colorspaceDPtr->gamma = rCurve.m_fun.m_g;
+ } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isSRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
+ colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
+ } else {
+ colorspaceDPtr->trc[0] = rCurve;
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
+ }
+ colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
+ colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
+ } else {
+ colorspaceDPtr->trc[0] = rCurve;
+ colorspaceDPtr->trc[1] = gCurve;
+ colorspaceDPtr->trc[2] = bCurve;
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
+ }
+ return true;
+}
+
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
{
if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
@@ -639,7 +1291,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
return false;
}
- if (tagTable.size < 12) {
+ if (tagTable.size < 8) {
qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
return false;
}
@@ -657,146 +1309,92 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
tagIndex.insert(Tag(quint32(tagTable.signature)), { tagTable.offset, tagTable.size });
}
- // Check the profile is three-component matrix based (what we currently support):
+ bool threeComponentMatrix = true;
+
if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
+ // Check the profile is three-component matrix based:
if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
!tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
- !tagIndex.contains(Tag::wtpt)) {
- qCInfo(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
- return false;
+ !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
+ threeComponentMatrix = false;
+ // Check if the profile is valid n-LUT based:
+ if (!tagIndex.contains(Tag::A2B0)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
+ return false;
+ }
}
- } else {
- Q_ASSERT(header.inputColorSpace == uint(ColorSpaceType::Gray));
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
return false;
}
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) {
+ threeComponentMatrix = false;
+ if (!tagIndex.contains(Tag::A2B0)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
+ return false;
+ }
+ } else {
+ Q_UNREACHABLE();
}
colorSpace->detach();
QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace);
- if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
- // Parse XYZ tags
- if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
- return false;
- if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
- return false;
- if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
- return false;
- if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
- return false;
+ if (threeComponentMatrix) {
+ colorspaceDPtr->isPcsLab = false;
+ colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
- colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
- } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
- } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
- qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
- }
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
- }
- } else {
- // We will use sRGB primaries and fit to match the given white-point if
- // it doesn't match sRGB's.
- QColorVector whitePoint;
- if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
- return false;
- if (!qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
- return false;
- }
- if (whitePoint == QColorVector::D65()) {
- colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
+ if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
+ if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr))
+ return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
+ if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
+ return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
} else {
- colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
- // Calculate chromaticity from xyz (assuming y == 1.0f).
- float y = 1.0f / (1.0f + whitePoint.z + whitePoint.x);
- float x = whitePoint.x * y;
- QColorSpacePrimaries primaries(QColorSpace::Primaries::SRgb);
- primaries.whitePoint = QPointF(x,y);
- if (!primaries.areValid()) {
- qCWarning(lcIcc, "fromIccProfile: Invalid ICC profile - invalid white-point(%f, %f)", x, y);
+ Q_UNREACHABLE();
+ }
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
return false;
- }
- colorspaceDPtr->toXyz = primaries.toXyzMatrix();
+ } else {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
}
- }
- // Reset the matrix to our canonical values:
- if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
- colorspaceDPtr->setToXyzMatrix();
+ if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ colorspaceDPtr->toXyz = colorspaceDPtr->chad;
- // Parse TRC tags
- TagEntry rTrc;
- TagEntry gTrc;
- TagEntry bTrc;
- if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
- rTrc = tagIndex[Tag::kTRC];
- gTrc = tagIndex[Tag::kTRC];
- bTrc = tagIndex[Tag::kTRC];
- } else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
- // Apple extension for parametric version of TRCs in ICCv2:
- rTrc = tagIndex[Tag::aarg];
- gTrc = tagIndex[Tag::aagg];
- bTrc = tagIndex[Tag::aabg];
+ // Reset the matrix to our canonical values:
+ if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
+ colorspaceDPtr->setToXyzMatrix();
+
+ if (!parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
+ return false;
} else {
- rTrc = tagIndex[Tag::rTRC];
- gTrc = tagIndex[Tag::gTRC];
- bTrc = tagIndex[Tag::bTRC];
- }
+ colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
+ colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
+ if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
+ else
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
- QColorTrc rCurve;
- QColorTrc gCurve;
- QColorTrc bCurve;
- if (!parseTRC(data, rTrc, rCurve)) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
- return false;
- }
- if (!parseTRC(data, gTrc, gCurve)) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
- return false;
- }
- if (!parseTRC(data, bTrc, bCurve)) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
- return false;
- }
- if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) {
- if (rCurve.m_fun.isLinear()) {
- qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
- colorspaceDPtr->trc[0] = QColorTransferFunction();
- colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
- colorspaceDPtr->gamma = 1.0f;
- } else if (rCurve.m_fun.isGamma()) {
- qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
- colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
- colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
- colorspaceDPtr->gamma = rCurve.m_fun.m_g;
- } else if (rCurve.m_fun.isSRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
- colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
- colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
- } else {
- colorspaceDPtr->trc[0] = rCurve;
- colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
+ // Only parse the default perceptual transform for now
+ if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
+ return false;
+ if (auto it = tagIndex.constFind(Tag::B2A0); it != tagIndex.constEnd()) {
+ if (!parseA2B(data, it.value(), colorspaceDPtr, false))
+ return false;
}
- colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
- colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
- } else {
- colorspaceDPtr->trc[0] = rCurve;
- colorspaceDPtr->trc[1] = gCurve;
- colorspaceDPtr->trc[2] = bCurve;
- colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
+ if (auto it = tagIndex.constFind(Tag::wtpt); it != tagIndex.constEnd()) {
+ if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
+ return false;
+ }
}
- if (tagIndex.contains(Tag::desc)) {
- if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
+ if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) {
+ if (!parseDesc(data, it.value(), colorspaceDPtr->description))
qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
else
qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
@@ -808,6 +1406,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->iccProfile = data;
+ Q_ASSERT(colorspaceDPtr->isValid());
return true;
}
diff --git a/src/gui/painting/qimageeffects.cpp b/src/gui/painting/qimageeffects.cpp
new file mode 100644
index 0000000000..7c2b947e08
--- /dev/null
+++ b/src/gui/painting/qimageeffects.cpp
@@ -0,0 +1,327 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qmath.h"
+#include "qdrawhelper_p.h"
+#include "qmemrotate_p.h"
+#include "qpainter.h"
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+template <int shift>
+inline int qt_static_shift(int value)
+{
+ if (shift == 0)
+ return value;
+ else if (shift > 0)
+ return value << (uint(shift) & 0x1f);
+ else
+ return value >> (uint(-shift) & 0x1f);
+}
+
+template<int aprec, int zprec>
+inline void qt_blurinner(uchar *bptr, int &zR, int &zG, int &zB, int &zA, int alpha)
+{
+ QRgb *pixel = (QRgb *)bptr;
+
+#define Z_MASK (0xff << zprec)
+ const int A_zprec = qt_static_shift<zprec - 24>(*pixel) & Z_MASK;
+ const int R_zprec = qt_static_shift<zprec - 16>(*pixel) & Z_MASK;
+ const int G_zprec = qt_static_shift<zprec - 8>(*pixel) & Z_MASK;
+ const int B_zprec = qt_static_shift<zprec>(*pixel) & Z_MASK;
+#undef Z_MASK
+
+ const int zR_zprec = zR >> aprec;
+ const int zG_zprec = zG >> aprec;
+ const int zB_zprec = zB >> aprec;
+ const int zA_zprec = zA >> aprec;
+
+ zR += alpha * (R_zprec - zR_zprec);
+ zG += alpha * (G_zprec - zG_zprec);
+ zB += alpha * (B_zprec - zB_zprec);
+ zA += alpha * (A_zprec - zA_zprec);
+
+#define ZA_MASK (0xff << (zprec + aprec))
+ *pixel =
+ qt_static_shift<24 - zprec - aprec>(zA & ZA_MASK)
+ | qt_static_shift<16 - zprec - aprec>(zR & ZA_MASK)
+ | qt_static_shift<8 - zprec - aprec>(zG & ZA_MASK)
+ | qt_static_shift<-zprec - aprec>(zB & ZA_MASK);
+#undef ZA_MASK
+}
+
+const int alphaIndex = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3);
+
+template<int aprec, int zprec>
+inline void qt_blurinner_alphaOnly(uchar *bptr, int &z, int alpha)
+{
+ const int A_zprec = int(*(bptr)) << zprec;
+ const int z_zprec = z >> aprec;
+ z += alpha * (A_zprec - z_zprec);
+ *(bptr) = z >> (zprec + aprec);
+}
+
+template<int aprec, int zprec, bool alphaOnly>
+inline void qt_blurrow(QImage & im, int line, int alpha)
+{
+ uchar *bptr = im.scanLine(line);
+
+ int zR = 0, zG = 0, zB = 0, zA = 0;
+
+ if (alphaOnly && im.format() != QImage::Format_Indexed8)
+ bptr += alphaIndex;
+
+ const int stride = im.depth() >> 3;
+ const int im_width = im.width();
+ for (int index = 0; index < im_width; ++index) {
+ if (alphaOnly)
+ qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+ else
+ qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+ bptr += stride;
+ }
+
+ bptr -= stride;
+
+ for (int index = im_width - 2; index >= 0; --index) {
+ bptr -= stride;
+ if (alphaOnly)
+ qt_blurinner_alphaOnly<aprec, zprec>(bptr, zA, alpha);
+ else
+ qt_blurinner<aprec, zprec>(bptr, zR, zG, zB, zA, alpha);
+ }
+}
+
+/*
+* expblur(QImage &img, int radius)
+*
+* Based on exponential blur algorithm by Jani Huhtanen
+*
+* In-place blur of image 'img' with kernel
+* of approximate radius 'radius'.
+*
+* Blurs with two sided exponential impulse
+* response.
+*
+* aprec = precision of alpha parameter
+* in fixed-point format 0.aprec
+*
+* zprec = precision of state parameters
+* zR,zG,zB and zA in fp format 8.zprec
+*/
+template <int aprec, int zprec, bool alphaOnly>
+void expblur(QImage &img, qreal radius, bool improvedQuality = false, int transposed = 0)
+{
+ // halve the radius if we're using two passes
+ if (improvedQuality)
+ radius *= qreal(0.5);
+
+ Q_ASSERT(img.format() == QImage::Format_ARGB32_Premultiplied
+ || img.format() == QImage::Format_RGB32
+ || img.format() == QImage::Format_Indexed8
+ || img.format() == QImage::Format_Grayscale8);
+
+ // choose the alpha such that pixels at radius distance from a fully
+ // saturated pixel will have an alpha component of no greater than
+ // the cutOffIntensity
+ const qreal cutOffIntensity = 2;
+ int alpha = radius <= qreal(1e-5)
+ ? ((1 << aprec)-1)
+ : qRound((1<<aprec)*(1 - qPow(cutOffIntensity * (1 / qreal(255)), 1 / radius)));
+
+ int img_height = img.height();
+ for (int row = 0; row < img_height; ++row) {
+ for (int i = 0; i <= int(improvedQuality); ++i)
+ qt_blurrow<aprec, zprec, alphaOnly>(img, row, alpha);
+ }
+
+ QImage temp(img.height(), img.width(), img.format());
+ temp.setDevicePixelRatio(img.devicePixelRatio());
+ if (transposed >= 0) {
+ if (img.depth() == 8) {
+ qt_memrotate270(reinterpret_cast<const quint8*>(img.bits()),
+ img.width(), img.height(), img.bytesPerLine(),
+ reinterpret_cast<quint8*>(temp.bits()),
+ temp.bytesPerLine());
+ } else {
+ qt_memrotate270(reinterpret_cast<const quint32*>(img.bits()),
+ img.width(), img.height(), img.bytesPerLine(),
+ reinterpret_cast<quint32*>(temp.bits()),
+ temp.bytesPerLine());
+ }
+ } else {
+ if (img.depth() == 8) {
+ qt_memrotate90(reinterpret_cast<const quint8*>(img.bits()),
+ img.width(), img.height(), img.bytesPerLine(),
+ reinterpret_cast<quint8*>(temp.bits()),
+ temp.bytesPerLine());
+ } else {
+ qt_memrotate90(reinterpret_cast<const quint32*>(img.bits()),
+ img.width(), img.height(), img.bytesPerLine(),
+ reinterpret_cast<quint32*>(temp.bits()),
+ temp.bytesPerLine());
+ }
+ }
+
+ img_height = temp.height();
+ for (int row = 0; row < img_height; ++row) {
+ for (int i = 0; i <= int(improvedQuality); ++i)
+ qt_blurrow<aprec, zprec, alphaOnly>(temp, row, alpha);
+ }
+
+ if (transposed == 0) {
+ if (img.depth() == 8) {
+ qt_memrotate90(reinterpret_cast<const quint8*>(temp.bits()),
+ temp.width(), temp.height(), temp.bytesPerLine(),
+ reinterpret_cast<quint8*>(img.bits()),
+ img.bytesPerLine());
+ } else {
+ qt_memrotate90(reinterpret_cast<const quint32*>(temp.bits()),
+ temp.width(), temp.height(), temp.bytesPerLine(),
+ reinterpret_cast<quint32*>(img.bits()),
+ img.bytesPerLine());
+ }
+ } else {
+ img = temp;
+ }
+}
+
+} // namespace
+
+#define AVG(a,b) ( ((((a)^(b)) & 0xfefefefeUL) >> 1) + ((a)&(b)) )
+#define AVG16(a,b) ( ((((a)^(b)) & 0xf7deUL) >> 1) + ((a)&(b)) )
+
+QImage qt_halfScaled(const QImage &source)
+{
+ if (source.width() < 2 || source.height() < 2)
+ return QImage();
+
+ QImage srcImage = source;
+
+ if (source.format() == QImage::Format_Indexed8 || source.format() == QImage::Format_Grayscale8) {
+ // assumes grayscale
+ QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+ dest.setDevicePixelRatio(source.devicePixelRatio());
+
+ const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+ qsizetype sx = srcImage.bytesPerLine();
+ qsizetype sx2 = sx << 1;
+
+ uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+ qsizetype dx = dest.bytesPerLine();
+ int ww = dest.width();
+ int hh = dest.height();
+
+ for (int y = hh; y; --y, dst += dx, src += sx2) {
+ const uchar *p1 = src;
+ const uchar *p2 = src + sx;
+ uchar *q = dst;
+ for (int x = ww; x; --x, ++q, p1 += 2, p2 += 2)
+ *q = ((int(p1[0]) + int(p1[1]) + int(p2[0]) + int(p2[1])) + 2) >> 2;
+ }
+
+ return dest;
+ } else if (source.format() == QImage::Format_ARGB8565_Premultiplied) {
+ QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+ dest.setDevicePixelRatio(source.devicePixelRatio());
+
+ const uchar *src = reinterpret_cast<const uchar*>(const_cast<const QImage &>(srcImage).bits());
+ qsizetype sx = srcImage.bytesPerLine();
+ qsizetype sx2 = sx << 1;
+
+ uchar *dst = reinterpret_cast<uchar*>(dest.bits());
+ qsizetype dx = dest.bytesPerLine();
+ int ww = dest.width();
+ int hh = dest.height();
+
+ for (int y = hh; y; --y, dst += dx, src += sx2) {
+ const uchar *p1 = src;
+ const uchar *p2 = src + sx;
+ uchar *q = dst;
+ for (int x = ww; x; --x, q += 3, p1 += 6, p2 += 6) {
+ // alpha
+ q[0] = AVG(AVG(p1[0], p1[3]), AVG(p2[0], p2[3]));
+ // rgb
+ const quint16 p16_1 = (p1[2] << 8) | p1[1];
+ const quint16 p16_2 = (p1[5] << 8) | p1[4];
+ const quint16 p16_3 = (p2[2] << 8) | p2[1];
+ const quint16 p16_4 = (p2[5] << 8) | p2[4];
+ const quint16 result = AVG16(AVG16(p16_1, p16_2), AVG16(p16_3, p16_4));
+ q[1] = result & 0xff;
+ q[2] = result >> 8;
+ }
+ }
+
+ return dest;
+ } else if (source.format() != QImage::Format_ARGB32_Premultiplied
+ && source.format() != QImage::Format_RGB32)
+ {
+ srcImage = source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ }
+
+ QImage dest(source.width() / 2, source.height() / 2, srcImage.format());
+ dest.setDevicePixelRatio(source.devicePixelRatio());
+
+ const quint32 *src = reinterpret_cast<const quint32*>(const_cast<const QImage &>(srcImage).bits());
+ qsizetype sx = srcImage.bytesPerLine() >> 2;
+ qsizetype sx2 = sx << 1;
+
+ quint32 *dst = reinterpret_cast<quint32*>(dest.bits());
+ qsizetype dx = dest.bytesPerLine() >> 2;
+ int ww = dest.width();
+ int hh = dest.height();
+
+ for (int y = hh; y; --y, dst += dx, src += sx2) {
+ const quint32 *p1 = src;
+ const quint32 *p2 = src + sx;
+ quint32 *q = dst;
+ for (int x = ww; x; --x, q++, p1 += 2, p2 += 2)
+ *q = AVG(AVG(p1[0], p1[1]), AVG(p2[0], p2[1]));
+ }
+
+ return dest;
+}
+
+#undef AVG
+#undef AVG16
+
+Q_GUI_EXPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0)
+{
+ if (blurImage.format() != QImage::Format_ARGB32_Premultiplied
+ && blurImage.format() != QImage::Format_RGB32)
+ {
+ blurImage = std::move(blurImage).convertToFormat(QImage::Format_ARGB32_Premultiplied);
+ }
+
+ qreal scale = 1;
+ if (radius >= 4 && blurImage.width() >= 2 && blurImage.height() >= 2) {
+ blurImage = qt_halfScaled(blurImage);
+ scale = 2;
+ radius *= qreal(0.5);
+ }
+
+ if (alphaOnly)
+ expblur<12, 10, true>(blurImage, radius, quality, transposed);
+ else
+ expblur<12, 10, false>(blurImage, radius, quality, transposed);
+
+ if (p) {
+ p->scale(scale, scale);
+ p->setRenderHint(QPainter::SmoothPixmapTransform);
+ p->drawImage(QRect(QPoint(0, 0), blurImage.deviceIndependentSize().toSize()), blurImage);
+ }
+}
+
+Q_GUI_EXPORT void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0)
+{
+ if (blurImage.format() == QImage::Format_Indexed8 || blurImage.format() == QImage::Format_Grayscale8)
+ expblur<12, 10, true>(blurImage, radius, quality, transposed);
+ else
+ expblur<12, 10, false>(blurImage, radius, quality, transposed);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/painting/qoutlinemapper.cpp b/src/gui/painting/qoutlinemapper.cpp
index 93eac5cced..2f87ff43b7 100644
--- a/src/gui/painting/qoutlinemapper.cpp
+++ b/src/gui/painting/qoutlinemapper.cpp
@@ -50,7 +50,7 @@ void QOutlineMapper::setClipRect(QRect clipRect)
if (clipRect != m_clip_rect) {
m_clip_rect = limitCoords(clipRect);
- const int mw = 64; // margin width. No need to trigger clipping for slight overshooting
+ const int mw = 1 << 10; // margin width. No need to trigger clipping for slight overshooting
m_clip_trigger_rect = QRectF(limitCoords(m_clip_rect.adjusted(-mw, -mw, mw, mw)));
}
}
diff --git a/src/gui/painting/qpagelayout.cpp b/src/gui/painting/qpagelayout.cpp
index 456fcf8802..e60f464d6e 100644
--- a/src/gui/painting/qpagelayout.cpp
+++ b/src/gui/painting/qpagelayout.cpp
@@ -39,41 +39,19 @@ Q_GUI_EXPORT qreal qt_pointMultiplier(QPageLayout::Unit unit)
// Multiplier for converting pixels to points.
extern qreal qt_pixelMultiplier(int resolution);
-QPointF qt_convertPoint(const QPointF &xy, QPageLayout::Unit fromUnits, QPageLayout::Unit toUnits)
-{
- // If the size have the same units, or are all 0, then don't need to convert
- if (fromUnits == toUnits || xy.isNull())
- return xy;
-
- // If converting to points then convert and round to 0 decimal places
- if (toUnits == QPageLayout::Point) {
- const qreal multiplier = qt_pointMultiplier(fromUnits);
- return QPointF(qRound(xy.x() * multiplier),
- qRound(xy.y() * multiplier));
- }
-
- // If converting to other units, need to convert to unrounded points first
- QPointF pointXy = (fromUnits == QPageLayout::Point) ? xy : xy * qt_pointMultiplier(fromUnits);
-
- // Then convert from points to required units rounded to 2 decimal places
- const qreal multiplier = qt_pointMultiplier(toUnits);
- return QPointF(qRound(pointXy.x() * 100 / multiplier) / 100.0,
- qRound(pointXy.y() * 100 / multiplier) / 100.0);
-}
-
Q_GUI_EXPORT QMarginsF qt_convertMargins(const QMarginsF &margins, QPageLayout::Unit fromUnits, QPageLayout::Unit toUnits)
{
// If the margins have the same units, or are all 0, then don't need to convert
if (fromUnits == toUnits || margins.isNull())
return margins;
- // If converting to points then convert and round to 0 decimal places
+ // If converting to points then convert and round up to 2 decimal places
if (toUnits == QPageLayout::Point) {
- const qreal multiplier = qt_pointMultiplier(fromUnits);
- return QMarginsF(qRound(margins.left() * multiplier),
- qRound(margins.top() * multiplier),
- qRound(margins.right() * multiplier),
- qRound(margins.bottom() * multiplier));
+ const qreal multiplierX100 = qt_pointMultiplier(fromUnits) * 100;
+ return QMarginsF(qCeil(margins.left() * multiplierX100) / 100.0,
+ qCeil(margins.top() * multiplierX100) / 100.0,
+ qCeil(margins.right() * multiplierX100) / 100.0,
+ qCeil(margins.bottom() * multiplierX100) / 100.0);
}
// If converting to other units, need to convert to unrounded points first
@@ -101,10 +79,10 @@ public:
bool isValid() const;
- void clampMargins(const QMarginsF &margins);
+ QMarginsF clampMargins(const QMarginsF &margins) const;
QMarginsF margins(QPageLayout::Unit units) const;
- QMargins marginsPoints() const;
+ QMarginsF marginsPoints() const;
QMargins marginsPixels(int resolution) const;
void setDefaultMargins(const QMarginsF &minMargins);
@@ -173,12 +151,12 @@ bool QPageLayoutPrivate::isValid() const
return m_pageSize.isValid();
}
-void QPageLayoutPrivate::clampMargins(const QMarginsF &margins)
+QMarginsF QPageLayoutPrivate::clampMargins(const QMarginsF &margins) const
{
- m_margins = QMarginsF(qBound(m_minMargins.left(), margins.left(), m_maxMargins.left()),
- qBound(m_minMargins.top(), margins.top(), m_maxMargins.top()),
- qBound(m_minMargins.right(), margins.right(), m_maxMargins.right()),
- qBound(m_minMargins.bottom(), margins.bottom(), m_maxMargins.bottom()));
+ return QMarginsF(qBound(m_minMargins.left(), margins.left(), m_maxMargins.left()),
+ qBound(m_minMargins.top(), margins.top(), m_maxMargins.top()),
+ qBound(m_minMargins.right(), margins.right(), m_maxMargins.right()),
+ qBound(m_minMargins.bottom(), margins.bottom(), m_maxMargins.bottom()));
}
QMarginsF QPageLayoutPrivate::margins(QPageLayout::Unit units) const
@@ -186,14 +164,14 @@ QMarginsF QPageLayoutPrivate::margins(QPageLayout::Unit units) const
return qt_convertMargins(m_margins, m_units, units);
}
-QMargins QPageLayoutPrivate::marginsPoints() const
+QMarginsF QPageLayoutPrivate::marginsPoints() const
{
- return qt_convertMargins(m_margins, m_units, QPageLayout::Point).toMargins();
+ return qt_convertMargins(m_margins, m_units, QPageLayout::Point);
}
QMargins QPageLayoutPrivate::marginsPixels(int resolution) const
{
- return marginsPoints() / qt_pixelMultiplier(resolution);
+ return QMarginsF(marginsPoints() / qt_pixelMultiplier(resolution)).toMargins();
}
void QPageLayoutPrivate::setDefaultMargins(const QMarginsF &minMargins)
@@ -204,7 +182,7 @@ void QPageLayoutPrivate::setDefaultMargins(const QMarginsF &minMargins)
qMax(m_fullSize.width() - m_minMargins.left(), qreal(0)),
qMax(m_fullSize.height() - m_minMargins.top(), qreal(0)));
if (m_mode == QPageLayout::StandardMode)
- clampMargins(m_margins);
+ m_margins = clampMargins(m_margins);
}
QSizeF QPageLayoutPrivate::fullSizeUnits(QPageLayout::Unit units) const
@@ -310,6 +288,27 @@ QRectF QPageLayoutPrivate::paintRect() const
\value StandardMode Paint Rect includes margins, margins must fall between the minimum and maximum.
\value FullPageMode Paint Rect excludes margins, margins can be any value and must be managed manually.
+
+ In StandardMode, when setting margins, use \l{QPageLayout::OutOfBoundsPolicy::}{Clamp} to
+ automatically clamp the margins to fall between the minimum and maximum
+ allowed values.
+
+ \sa OutOfBoundsPolicy
+*/
+
+/*!
+ \enum QPageLayout::OutOfBoundsPolicy
+ \since 6.8
+
+ Defines the policy for margins that are out of bounds
+
+ \value Reject The margins must fall within the minimum and maximum values,
+ otherwise they will be rejected.
+ \value Clamp The margins are clamped between the minimum and maximum
+ values to ensure they are valid.
+
+ \note The policy has no effect in \l{QPageLayout::Mode}{FullPageMode},
+ where all margins are accepted.
*/
/*!
@@ -547,39 +546,52 @@ QPageLayout::Unit QPageLayout::units() const
}
/*!
- Sets the page margins of the page layout to \a margins
+ Sets the page margins of the page layout to \a margins.
Returns true if the margins were successfully set.
The units used are those currently defined for the layout. To use different
units then call setUnits() first.
- If in the default StandardMode then all the new margins must fall between the
- minimum margins set and the maximum margins allowed by the page size,
- otherwise the margins will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa margins(), units()
*/
-bool QPageLayout::setMargins(const QMarginsF &margins)
+bool QPageLayout::setMargins(const QMarginsF &margins, OutOfBoundsPolicy outOfBoundsPolicy)
{
if (d->m_mode == FullPageMode) {
- d.detach();
- d->m_margins = margins;
+ if (margins != d->m_margins) {
+ d.detach();
+ d->m_margins = margins;
+ }
return true;
- } else if (margins.left() >= d->m_minMargins.left()
- && margins.right() >= d->m_minMargins.right()
- && margins.top() >= d->m_minMargins.top()
- && margins.bottom() >= d->m_minMargins.bottom()
- && margins.left() <= d->m_maxMargins.left()
- && margins.right() <= d->m_maxMargins.right()
- && margins.top() <= d->m_maxMargins.top()
- && margins.bottom() <= d->m_maxMargins.bottom()) {
- d.detach();
- d->m_margins = margins;
+ }
+
+ if (outOfBoundsPolicy == OutOfBoundsPolicy::Clamp) {
+ const QMarginsF clampedMargins = d->clampMargins(margins);
+ if (clampedMargins != d->m_margins) {
+ d.detach();
+ d->m_margins = clampedMargins;
+ }
return true;
}
+
+ if (margins.left() >= d->m_minMargins.left()
+ && margins.right() >= d->m_minMargins.right()
+ && margins.top() >= d->m_minMargins.top()
+ && margins.bottom() >= d->m_minMargins.bottom()
+ && margins.left() <= d->m_maxMargins.left()
+ && margins.right() <= d->m_maxMargins.right()
+ && margins.top() <= d->m_maxMargins.top()
+ && margins.bottom() <= d->m_maxMargins.bottom()) {
+ if (margins != d->m_margins) {
+ d.detach();
+ d->m_margins = margins;
+ }
+ return true;
+ }
+
return false;
}
@@ -590,23 +602,27 @@ bool QPageLayout::setMargins(const QMarginsF &margins)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setLeftMargin(qreal leftMargin)
+bool QPageLayout::setLeftMargin(qreal leftMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ leftMargin = qBound(d->m_minMargins.left(), leftMargin, d->m_maxMargins.left());
+
+ if (qFuzzyCompare(leftMargin, d->m_margins.left()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (leftMargin >= d->m_minMargins.left() && leftMargin <= d->m_maxMargins.left())) {
d.detach();
d->m_margins.setLeft(leftMargin);
return true;
}
+
return false;
}
@@ -617,23 +633,27 @@ bool QPageLayout::setLeftMargin(qreal leftMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setRightMargin(qreal rightMargin)
+bool QPageLayout::setRightMargin(qreal rightMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ rightMargin = qBound(d->m_minMargins.right(), rightMargin, d->m_maxMargins.right());
+
+ if (qFuzzyCompare(rightMargin, d->m_margins.right()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (rightMargin >= d->m_minMargins.right() && rightMargin <= d->m_maxMargins.right())) {
d.detach();
d->m_margins.setRight(rightMargin);
return true;
}
+
return false;
}
@@ -644,23 +664,27 @@ bool QPageLayout::setRightMargin(qreal rightMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setTopMargin(qreal topMargin)
+bool QPageLayout::setTopMargin(qreal topMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ topMargin = qBound(d->m_minMargins.top(), topMargin, d->m_maxMargins.top());
+
+ if (qFuzzyCompare(topMargin, d->m_margins.top()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (topMargin >= d->m_minMargins.top() && topMargin <= d->m_maxMargins.top())) {
d.detach();
d->m_margins.setTop(topMargin);
return true;
}
+
return false;
}
@@ -671,23 +695,27 @@ bool QPageLayout::setTopMargin(qreal topMargin)
The units used are those currently defined for the layout. To use different
units call setUnits() first.
- If in the default StandardMode then the new margin must fall between the
- minimum margin set and the maximum margin allowed by the page size,
- otherwise the margin will not be set.
-
- If in FullPageMode then any margin values will be accepted.
+ Since Qt 6.8, the optional \a outOfBoundsPolicy can be used to specify how
+ margins that are out of bounds are handled.
\sa setMargins(), margins()
*/
-bool QPageLayout::setBottomMargin(qreal bottomMargin)
+bool QPageLayout::setBottomMargin(qreal bottomMargin, OutOfBoundsPolicy outOfBoundsPolicy)
{
+ if (d->m_mode == StandardMode && outOfBoundsPolicy == OutOfBoundsPolicy::Clamp)
+ bottomMargin = qBound(d->m_minMargins.bottom(), bottomMargin, d->m_maxMargins.bottom());
+
+ if (qFuzzyCompare(bottomMargin, d->m_margins.bottom()))
+ return true;
+
if (d->m_mode == FullPageMode
|| (bottomMargin >= d->m_minMargins.bottom() && bottomMargin <= d->m_maxMargins.bottom())) {
d.detach();
d->m_margins.setBottom(bottomMargin);
return true;
}
+
return false;
}
@@ -721,7 +749,7 @@ QMarginsF QPageLayout::margins(Unit units) const
QMargins QPageLayout::marginsPoints() const
{
- return d->marginsPoints();
+ return d->marginsPoints().toMargins();
}
/*!
@@ -888,7 +916,7 @@ QRect QPageLayout::paintRectPoints() const
if (!isValid())
return QRect();
return d->m_mode == FullPageMode ? d->fullRectPoints()
- : d->fullRectPoints() - d->marginsPoints();
+ : d->fullRectPoints() - d->marginsPoints().toMargins();
}
/*!
diff --git a/src/gui/painting/qpagelayout.h b/src/gui/painting/qpagelayout.h
index 29be2f9725..1e340b6d75 100644
--- a/src/gui/painting/qpagelayout.h
+++ b/src/gui/painting/qpagelayout.h
@@ -40,6 +40,11 @@ public:
FullPageMode // Paint Rect excludes margins
};
+ enum class OutOfBoundsPolicy {
+ Reject,
+ Clamp
+ };
+
QPageLayout();
QPageLayout(const QPageSize &pageSize, Orientation orientation,
const QMarginsF &margins, Unit units = Point,
@@ -68,11 +73,19 @@ public:
void setUnits(Unit units);
Unit units() const;
+#if QT_GUI_REMOVED_SINCE(6, 8)
bool setMargins(const QMarginsF &margins);
bool setLeftMargin(qreal leftMargin);
bool setRightMargin(qreal rightMargin);
bool setTopMargin(qreal topMargin);
bool setBottomMargin(qreal bottomMargin);
+#endif
+
+ bool setMargins(const QMarginsF &margins, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setLeftMargin(qreal leftMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setRightMargin(qreal rightMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setTopMargin(qreal topMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
+ bool setBottomMargin(qreal bottomMargin, OutOfBoundsPolicy outOfBoundsPolicy = OutOfBoundsPolicy::Reject);
QMarginsF margins() const;
QMarginsF margins(Unit units) const;
diff --git a/src/gui/painting/qpageranges.cpp b/src/gui/painting/qpageranges.cpp
index 500673b22f..99a0009883 100644
--- a/src/gui/painting/qpageranges.cpp
+++ b/src/gui/painting/qpageranges.cpp
@@ -255,7 +255,7 @@ int QPageRanges::firstPage() const
{
if (isEmpty())
return 0;
- return d->intervals.first().from;
+ return d->intervals.constFirst().from;
}
/*!
@@ -266,7 +266,7 @@ int QPageRanges::lastPage() const
{
if (isEmpty())
return 0;
- return d->intervals.last().to;
+ return d->intervals.constLast().to;
}
/*!
diff --git a/src/gui/painting/qpagesize.cpp b/src/gui/painting/qpagesize.cpp
index 3ed633bf99..37f66fe63d 100644
--- a/src/gui/painting/qpagesize.cpp
+++ b/src/gui/painting/qpagesize.cpp
@@ -543,7 +543,7 @@ static QSize qt_convertPointsToPixels(const QSize &size, int resolution)
if (!size.isValid() || resolution <= 0)
return QSize();
const qreal multiplier = qt_pixelMultiplier(resolution);
- return QSize(qRound(size.width() / multiplier), qRound(size.height() / multiplier));
+ return QSize(qFloor(size.width() / multiplier), qFloor(size.height() / multiplier));
}
static QSizeF qt_convertPointsToUnits(const QSize &size, QPageSize::Unit units)
@@ -869,7 +869,7 @@ QSizeF QPageSizePrivate::size(QPageSize::Unit units) const
QSize QPageSizePrivate::sizePixels(int resolution) const
{
- return qt_convertPointsToPixels(m_pointSize, resolution);;
+ return qt_convertPointsToPixels(m_pointSize, resolution);
}
diff --git a/src/gui/painting/qpagesize.h b/src/gui/painting/qpagesize.h
index b3629bb8d2..221863aad7 100644
--- a/src/gui/painting/qpagesize.h
+++ b/src/gui/painting/qpagesize.h
@@ -203,7 +203,9 @@ public:
void swap(QPageSize &other) noexcept { d.swap(other.d); }
+#if QT_GUI_REMOVED_SINCE(6, 4)
friend Q_GUI_EXPORT bool operator==(const QPageSize &lhs, const QPageSize &rhs);
+#endif
bool isEquivalentTo(const QPageSize &other) const;
bool isValid() const;
diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp
index 839be3bd41..aface3744d 100644
--- a/src/gui/painting/qpaintengine.cpp
+++ b/src/gui/painting/qpaintengine.cpp
@@ -481,6 +481,8 @@ void QPaintEngine::drawEllipse(const QRectF &rect)
}
/*!
+ \overload
+
The default implementation of this function calls the floating
point version of this function
*/
diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp
index ac9af070f1..f62373d4ef 100644
--- a/src/gui/painting/qpaintengine_raster.cpp
+++ b/src/gui/painting/qpaintengine_raster.cpp
@@ -240,8 +240,7 @@ QRasterPaintEnginePrivate::QRasterPaintEnginePrivate() :
/*!
\class QRasterPaintEngine
- \preliminary
- \ingroup qws
+ \internal
\inmodule QtGui
\since 4.2
@@ -2262,7 +2261,7 @@ void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRe
|| d->rasterBuffer->compositionMode == QPainter::CompositionMode_Source))
{
RotationType rotationType = qRotationType(s->matrix);
- Q_ASSUME(d->rasterBuffer->format < QImage::NImageFormats);
+ Q_ASSERT(d->rasterBuffer->format < QImage::NImageFormats);
const QPixelLayout::BPP plBpp = qPixelLayouts[d->rasterBuffer->format].bpp;
if (rotationType != NoRotation && qMemRotateFunctions[plBpp][rotationType] && img.rect().contains(sr.toAlignedRect())) {
@@ -2343,9 +2342,16 @@ void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRe
}
SrcOverScaleFunc func = qScaleFunctions[d->rasterBuffer->format][img.format()];
if (func && (!clip || clip->hasRectClip)) {
+ QRectF tr = qt_mapRect_non_normalizing(r, s->matrix);
+ if (!s->flags.antialiased) {
+ tr.setX(qRound(tr.x()));
+ tr.setY(qRound(tr.y()));
+ tr.setWidth(qRound(tr.width()));
+ tr.setHeight(qRound(tr.height()));
+ }
func(d->rasterBuffer->buffer(), d->rasterBuffer->bytesPerLine(),
img.bits(), img.bytesPerLine(), img.height(),
- qt_mapRect_non_normalizing(r, s->matrix), sr,
+ tr, sr,
!clip ? d->deviceRect : clip->clipRect,
s->intOpacity);
return;
@@ -3391,16 +3397,18 @@ void QRasterPaintEngine::drawBitmap(const QPointF &pos, const QImage &image, QSp
// Boundaries
int w = image.width();
int h = image.height();
- int ymax = qMin(qRound(pos.y() + h), d->rasterBuffer->height());
- int ymin = qMax(qRound(pos.y()), 0);
- int xmax = qMin(qRound(pos.x() + w), d->rasterBuffer->width());
- int xmin = qMax(qRound(pos.x()), 0);
+ int px = qRound(pos.x());
+ int py = qRound(pos.y());
+ int ymax = qMin(py + h, d->rasterBuffer->height());
+ int ymin = qMax(py, 0);
+ int xmax = qMin(px + w, d->rasterBuffer->width());
+ int xmin = qMax(px, 0);
- int x_offset = xmin - qRound(pos.x());
+ int x_offset = xmin - px;
QImage::Format format = image.format();
for (int y = ymin; y < ymax; ++y) {
- const uchar *src = image.scanLine(y - qRound(pos.y()));
+ const uchar *src = image.scanLine(y - py);
if (format == QImage::Format_MonoLSB) {
for (int x = 0; x < xmax - xmin; ++x) {
int src_x = x + x_offset;
@@ -3705,15 +3713,9 @@ bool QRasterPaintEnginePrivate::canUseImageBlitting(QPainter::CompositionMode mo
QImage::Format dFormat = rasterBuffer->format;
QImage::Format sFormat = image.format();
- // Formats must match or source format must be a subset of destination format
- if (dFormat != sFormat && image.pixelFormat().alphaUsage() == QPixelFormat::IgnoresAlpha) {
- if ((sFormat == QImage::Format_RGB32 && dFormat == QImage::Format_ARGB32)
- || (sFormat == QImage::Format_RGBX8888 && dFormat == QImage::Format_RGBA8888)
- || (sFormat == QImage::Format_RGBX64 && dFormat == QImage::Format_RGBA64))
- sFormat = dFormat;
- else
- sFormat = qt_maybeAlphaVersionWithSameDepth(sFormat); // this returns premul formats
- }
+ // Formats must match or source format must be an opaque version of destination format
+ if (dFormat != sFormat && image.pixelFormat().alphaUsage() == QPixelFormat::IgnoresAlpha)
+ dFormat = qt_maybeDataCompatibleOpaqueVersion(dFormat);
return (dFormat == sFormat);
}
@@ -3800,7 +3802,7 @@ void QClipData::initialize()
return;
if (!m_clipLines)
- m_clipLines = (ClipLine *)calloc(sizeof(ClipLine), clipSpanHeight);
+ m_clipLines = (ClipLine *)calloc(clipSpanHeight, sizeof(ClipLine));
Q_CHECK_PTR(m_clipLines);
QT_TRY {
diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp
index 7f1c7e356d..9468876c23 100644
--- a/src/gui/painting/qpaintengineex.cpp
+++ b/src/gui/painting/qpaintengineex.cpp
@@ -902,7 +902,7 @@ void QPaintEngineEx::drawPoints(const QPoint *points, int pointCount)
void QPaintEngineEx::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
{
- Q_ASSUME(pointCount >= 2);
+ Q_ASSERT(pointCount >= 2);
QVectorPath path((const qreal *) points, pointCount, nullptr, QVectorPath::polygonFlags(mode));
if (mode == PolylineMode)
@@ -913,7 +913,7 @@ void QPaintEngineEx::drawPolygon(const QPointF *points, int pointCount, PolygonD
void QPaintEngineEx::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode)
{
- Q_ASSUME(pointCount >= 2);
+ Q_ASSERT(pointCount >= 2);
int count = pointCount<<1;
QVarLengthArray<qreal> pts(count);
diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp
index e69f369dbe..f4b9741eed 100644
--- a/src/gui/painting/qpainter.cpp
+++ b/src/gui/painting/qpainter.cpp
@@ -221,7 +221,7 @@ qreal QPainterPrivate::effectiveDevicePixelRatio() const
if (device->devType() == QInternal::Printer)
return qreal(1);
- return qMax(qreal(1), device->devicePixelRatio());
+ return device->devicePixelRatio();
}
QTransform QPainterPrivate::hidpiScaleTransform() const
@@ -1125,24 +1125,22 @@ void QPainterPrivate::updateState(QPainterState *newState)
The QPainter class also provides a means of controlling the
rendering quality through its RenderHint enum and the support for
floating point precision: All the functions for drawing primitives
- has a floating point version. These are often used in combination
+ have floating point versions.
+
+ \snippet code/src_gui_painting_qpainter.cpp floatBased
+
+ These are often used in combination
with the \l {RenderHint}{QPainter::Antialiasing} render hint.
+ \snippet code/src_gui_painting_qpainter.cpp renderHint
+
\table 100%
\row
+ \li Comparing concentric circles with int and float, and with or without
+ anti-aliased rendering. Using the floating point precision versions
+ produces evenly spaced rings. Anti-aliased rendering results in
+ smooth circles.
\li \inlineimage qpainter-concentriccircles.png
- \li
- \b {Concentric Circles Example}
-
- The \l {painting/concentriccircles}{Concentric Circles} example
- shows the improved rendering quality that can be obtained using
- floating point precision and anti-aliasing when drawing custom
- widgets.
-
- The application's main window displays several widgets which are
- drawn using the various combinations of precision and
- anti-aliasing.
-
\endtable
The RenderHint enum specifies flags to QPainter that may or may
@@ -1420,7 +1418,7 @@ void QPainterPrivate::updateState(QPainterState *newState)
This value was added in Qt 6.4.
\sa renderHints(), setRenderHint(), {QPainter#Rendering
- Quality}{Rendering Quality}, {Concentric Circles Example}
+ Quality}{Rendering Quality}
*/
@@ -1767,9 +1765,12 @@ bool QPainter::begin(QPaintDevice *pd)
qWarning("QPainter::begin: Cannot paint on a null image");
qt_cleanup_painter_state(d);
return false;
- } else if (img->format() == QImage::Format_Indexed8) {
- // Painting on indexed8 images is not supported.
- qWarning("QPainter::begin: Cannot paint on an image with the QImage::Format_Indexed8 format");
+ } else if (img->format() == QImage::Format_Indexed8 ||
+ img->format() == QImage::Format_CMYK8888) {
+ // Painting on these formats is not supported.
+ qWarning() << "QPainter::begin: Cannot paint on an image with the"
+ << img->format()
+ << "format";
qt_cleanup_painter_state(d);
return false;
}
@@ -1824,7 +1825,7 @@ bool QPainter::begin(QPaintDevice *pd)
Q_ASSERT(d->engine->isActive());
- if (!d->state->redirectionMatrix.isIdentity() || d->effectiveDevicePixelRatio() > 1)
+ if (!d->state->redirectionMatrix.isIdentity() || !qFuzzyCompare(d->effectiveDevicePixelRatio(), qreal(1.0)))
d->updateMatrix();
Q_ASSERT(d->engine->isActive());
@@ -3901,8 +3902,10 @@ void QPainter::drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius,
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawRoundedRect: Painter not active");
return;
+ }
if (xRadius <= 0 || yRadius <= 0) { // draw normal rectangle
drawRect(rect);
@@ -3963,8 +3966,10 @@ void QPainter::drawEllipse(const QRectF &r)
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawEllipse: Painter not active");
return;
+ }
QRectF rect(r.normalized());
@@ -4004,8 +4009,10 @@ void QPainter::drawEllipse(const QRect &r)
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawEllipse: Painter not active");
return;
+ }
QRect rect(r.normalized());
@@ -4091,8 +4098,10 @@ void QPainter::drawArc(const QRectF &r, int a, int alen)
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawArc: Painter not active");
return;
+ }
QRectF rect = r.normalized();
@@ -4153,8 +4162,10 @@ void QPainter::drawPie(const QRectF &r, int a, int alen)
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawPie: Painter not active");
return;
+ }
if (a > (360*16)) {
a = a % (360*16);
@@ -4222,8 +4233,10 @@ void QPainter::drawChord(const QRectF &r, int a, int alen)
#endif
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawChord: Painter not active");
return;
+ }
QRectF rect = r.normalized();
@@ -6509,8 +6522,10 @@ void QPainter::drawPicture(const QPointF &p, const QPicture &picture)
{
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::drawPicture: Painter not active");
return;
+ }
if (!d->extended)
d->updateState(d->state);
@@ -6621,8 +6636,10 @@ void QPainter::fillRect(const QRectF &r, const QBrush &brush)
{
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::fillRect: Painter not active");
return;
+ }
if (d->extended && !needsEmulation(brush)) {
d->extended->fillRect(r, brush);
@@ -6656,8 +6673,10 @@ void QPainter::fillRect(const QRect &r, const QBrush &brush)
{
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::fillRect: Painter not active");
return;
+ }
if (d->extended && !needsEmulation(brush)) {
d->extended->fillRect(r, brush);
@@ -6694,8 +6713,10 @@ void QPainter::fillRect(const QRect &r, const QColor &color)
{
Q_D(QPainter);
- if (!d->engine)
+ if (!d->engine) {
+ qWarning("QPainter::fillRect: Painter not active");
return;
+ }
if (d->extended) {
d->extended->fillRect(r, color);
diff --git a/src/gui/painting/qpainterpath.cpp b/src/gui/painting/qpainterpath.cpp
index 69473ef0f4..8d23d167b0 100644
--- a/src/gui/painting/qpainterpath.cpp
+++ b/src/gui/painting/qpainterpath.cpp
@@ -517,10 +517,8 @@ QPainterPath::QPainterPath(const QPainterPath &other) = default;
*/
QPainterPath::QPainterPath(const QPointF &startPoint)
- : d_ptr(new QPainterPathPrivate)
+ : d_ptr(new QPainterPathPrivate(startPoint))
{
- Element e = { startPoint.x(), startPoint.y(), MoveToElement };
- d_func()->elements << e;
}
void QPainterPath::detach()
@@ -1491,7 +1489,7 @@ QRectF QPainterPath::controlPointRect() const
bool QPainterPath::isEmpty() const
{
- return !d_ptr || (d_ptr->elements.size() == 1 && d_ptr->elements.first().type == MoveToElement);
+ return !d_ptr || (d_ptr->elements.size() == 1 && d_ptr->elements.constFirst().type == MoveToElement);
}
/*!
@@ -2444,14 +2442,14 @@ QDataStream &operator>>(QDataStream &s, QPainterPath &p)
int size;
s >> size;
- if (size == 0)
+ if (size == 0) {
+ p = {};
return s;
+ }
p.ensureData(); // in case if p.d_func() == 0
- if (p.d_func()->elements.size() == 1) {
- Q_ASSERT(p.d_func()->elements.at(0).type == QPainterPath::MoveToElement);
- p.d_func()->elements.clear();
- }
+ p.detach();
+ p.d_func()->elements.clear();
for (int i=0; i<size; ++i) {
int type;
double x, y;
@@ -2474,9 +2472,7 @@ QDataStream &operator>>(QDataStream &s, QPainterPath &p)
s >> fillRule;
Q_ASSERT(fillRule == Qt::OddEvenFill || fillRule == Qt::WindingFill);
p.d_func()->fillRule = Qt::FillRule(fillRule);
- p.d_func()->dirtyBounds = true;
- p.d_func()->dirtyControlBounds = true;
- if (errorDetected)
+ if (errorDetected || p.d_func()->elements.isEmpty())
p = QPainterPath(); // Better than to return path with possibly corrupt datastructure, which would likely cause crash
return s;
}
diff --git a/src/gui/painting/qpainterpath_p.h b/src/gui/painting/qpainterpath_p.h
index 55164bc347..a07b6cca37 100644
--- a/src/gui/painting/qpainterpath_p.h
+++ b/src/gui/painting/qpainterpath_p.h
@@ -119,6 +119,21 @@ public:
{
}
+ QPainterPathPrivate(QPointF startPoint)
+ : QSharedData(),
+ elements{ { startPoint.x(), startPoint.y(), QPainterPath::MoveToElement } },
+ cStart(0),
+ fillRule(Qt::OddEvenFill),
+ bounds(startPoint, QSizeF(0, 0)),
+ controlBounds(startPoint, QSizeF(0, 0)),
+ require_moveTo(false),
+ dirtyBounds(false),
+ dirtyControlBounds(false),
+ convex(false),
+ pathConverter(nullptr)
+ {
+ }
+
QPainterPathPrivate(const QPainterPathPrivate &other) noexcept
: QSharedData(other),
elements(other.elements),
diff --git a/src/gui/painting/qpathclipper.cpp b/src/gui/painting/qpathclipper.cpp
index 7ada65ce03..10a0639b53 100644
--- a/src/gui/painting/qpathclipper.cpp
+++ b/src/gui/painting/qpathclipper.cpp
@@ -650,7 +650,8 @@ int QKdPointTree::build(int begin, int end, int depth)
}
}
- qSwap(m_nodes.at(last), m_nodes.at(begin));
+ if (last != begin)
+ qSwap(m_nodes.at(last), m_nodes.at(begin));
if (last > begin)
m_nodes.at(last).left = &m_nodes.at(build(begin, last, depth + 1));
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp
index 02fd2fdeea..38f2a9b803 100644
--- a/src/gui/painting/qpdf.cpp
+++ b/src/gui/painting/qpdf.cpp
@@ -7,6 +7,7 @@
#include "qplatformdefs.h"
+#include <private/qcmyk_p.h>
#include <private/qfont_p.h>
#include <private/qmath_p.h>
#include <private/qpainter_p.h>
@@ -44,7 +45,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
+constexpr QPaintEngine::PaintEngineFeatures qt_pdf_decide_features()
{
QPaintEngine::PaintEngineFeatures f = QPaintEngine::AllFeatures;
f &= ~(QPaintEngine::PorterDuff
@@ -99,7 +100,7 @@ static void removeTransparencyFromBrush(QBrush &brush)
const char *qt_real_to_string(qreal val, char *buf) {
const char *ret = buf;
- if (qIsNaN(val)) {
+ if (!qIsFinite(val) || std::abs(val) > std::numeric_limits<quint32>::max()) {
*(buf++) = '0';
*(buf++) = ' ';
*buf = 0;
@@ -110,8 +111,8 @@ const char *qt_real_to_string(qreal val, char *buf) {
*(buf++) = '-';
val = -val;
}
- unsigned int ival = (unsigned int) val;
- qreal frac = val - (qreal)ival;
+ qreal frac = std::modf(val, &val);
+ quint32 ival(val);
int ifrac = (int)(frac * 1000000000);
if (ifrac == 1000000000) {
@@ -270,13 +271,6 @@ namespace QPdf {
dev->open(QIODevice::ReadWrite | QIODevice::Truncate);
}
- void ByteStream::constructor_helper(QByteArray *ba)
- {
- delete dev;
- dev = new QBuffer(ba);
- dev->open(QIODevice::ReadWrite);
- }
-
void ByteStream::prepareBuffer()
{
Q_ASSERT(!dev->isSequential());
@@ -285,16 +279,17 @@ namespace QPdf {
&& size > maxMemorySize()) {
// Switch to file backing.
QTemporaryFile *newFile = new QTemporaryFile;
- newFile->open();
- dev->reset();
- while (!dev->atEnd()) {
- QByteArray buf = dev->read(chunkSize());
- newFile->write(buf);
+ if (newFile->open()) {
+ dev->reset();
+ while (!dev->atEnd()) {
+ QByteArray buf = dev->read(chunkSize());
+ newFile->write(buf);
+ }
+ delete dev;
+ dev = newFile;
+ ba.clear();
+ fileBackingActive = true;
}
- delete dev;
- dev = newFile;
- ba.clear();
- fileBackingActive = true;
}
if (dev->pos() != size) {
dev->seek(size);
@@ -1239,17 +1234,8 @@ void QPdfEngine::setPen()
QBrush b = d->pen.brush();
Q_ASSERT(b.style() == Qt::SolidPattern && b.isOpaque());
- QColor rgba = b.color();
- if (d->grayscale) {
- qreal gray = qGray(rgba.rgba())/255.;
- *d->currentPage << gray << gray << gray;
- } else {
- *d->currentPage << rgba.redF()
- << rgba.greenF()
- << rgba.blueF();
- }
+ d->writeColor(QPdfEnginePrivate::ColorDomain::Stroking, b.color());
*d->currentPage << "SCN\n";
-
*d->currentPage << d->pen.widthF() << "w ";
int pdfCapStyle = 0;
@@ -1303,18 +1289,9 @@ void QPdfEngine::setBrush()
if (!patternObject && !specifyColor)
return;
- *d->currentPage << (patternObject ? "/PCSp cs " : "/CSp cs ");
- if (specifyColor) {
- QColor rgba = d->brush.color();
- if (d->grayscale) {
- qreal gray = qGray(rgba.rgba())/255.;
- *d->currentPage << gray << gray << gray;
- } else {
- *d->currentPage << rgba.redF()
- << rgba.greenF()
- << rgba.blueF();
- }
- }
+ const auto domain = patternObject ? QPdfEnginePrivate::ColorDomain::NonStrokingPattern
+ : QPdfEnginePrivate::ColorDomain::NonStroking;
+ d->writeColor(domain, specifyColor ? d->brush.color() : QColor());
if (patternObject)
*d->currentPage << "/Pat" << patternObject;
*d->currentPage << "scn\n";
@@ -1454,9 +1431,9 @@ int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const
QPdfEnginePrivate::QPdfEnginePrivate()
: clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false),
needsTransform(false), pdfVersion(QPdfEngine::Version_1_4),
+ colorModel(QPdfEngine::ColorModel::Auto),
outDevice(nullptr), ownsDevice(false),
embedFonts(true),
- grayscale(false),
m_pageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(10, 10, 10, 10))
{
initResources();
@@ -1511,7 +1488,9 @@ bool QPdfEngine::begin(QPaintDevice *pdev)
d->catalog = 0;
d->info = 0;
d->graphicsState = 0;
- d->patternColorSpace = 0;
+ d->patternColorSpaceRGB = 0;
+ d->patternColorSpaceGrayscale = 0;
+ d->patternColorSpaceCMYK = 0;
d->simplePen = false;
d->needsTransform = false;
@@ -1629,10 +1608,108 @@ void QPdfEnginePrivate::writeHeader()
">>\n"
"endobj\n");
- // color space for pattern
- patternColorSpace = addXrefEntry(-1);
+ // color spaces for pattern
+ patternColorSpaceRGB = addXrefEntry(-1);
xprintf("[/Pattern /DeviceRGB]\n"
"endobj\n");
+ patternColorSpaceGrayscale = addXrefEntry(-1);
+ xprintf("[/Pattern /DeviceGray]\n"
+ "endobj\n");
+ patternColorSpaceCMYK = addXrefEntry(-1);
+ xprintf("[/Pattern /DeviceCMYK]\n"
+ "endobj\n");
+}
+
+QPdfEngine::ColorModel QPdfEnginePrivate::colorModelForColor(const QColor &color) const
+{
+ switch (colorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ case QPdfEngine::ColorModel::Grayscale:
+ case QPdfEngine::ColorModel::CMYK:
+ return colorModel;
+ case QPdfEngine::ColorModel::Auto:
+ switch (color.spec()) {
+ case QColor::Invalid:
+ case QColor::Rgb:
+ case QColor::Hsv:
+ case QColor::Hsl:
+ case QColor::ExtendedRgb:
+ return QPdfEngine::ColorModel::RGB;
+ case QColor::Cmyk:
+ return QPdfEngine::ColorModel::CMYK;
+ }
+
+ break;
+ }
+
+ Q_UNREACHABLE_RETURN(QPdfEngine::ColorModel::RGB);
+}
+
+void QPdfEnginePrivate::writeColor(ColorDomain domain, const QColor &color)
+{
+ // Switch to the right colorspace.
+ // For simplicity: do it even if it redundant (= already in that colorspace)
+ const QPdfEngine::ColorModel actualColorModel = colorModelForColor(color);
+
+ switch (actualColorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ switch (domain) {
+ case ColorDomain::Stroking:
+ *currentPage << "/CSp CS\n"; break;
+ case ColorDomain::NonStroking:
+ *currentPage << "/CSp cs\n"; break;
+ case ColorDomain::NonStrokingPattern:
+ *currentPage << "/PCSp cs\n"; break;
+ }
+ break;
+ case QPdfEngine::ColorModel::Grayscale:
+ switch (domain) {
+ case ColorDomain::Stroking:
+ *currentPage << "/CSpg CS\n"; break;
+ case ColorDomain::NonStroking:
+ *currentPage << "/CSpg cs\n"; break;
+ case ColorDomain::NonStrokingPattern:
+ *currentPage << "/PCSpg cs\n"; break;
+ }
+ break;
+ case QPdfEngine::ColorModel::CMYK:
+ switch (domain) {
+ case ColorDomain::Stroking:
+ *currentPage << "/CSpcmyk CS\n"; break;
+ case ColorDomain::NonStroking:
+ *currentPage << "/CSpcmyk cs\n"; break;
+ case ColorDomain::NonStrokingPattern:
+ *currentPage << "/PCSpcmyk cs\n"; break;
+ }
+ break;
+ case QPdfEngine::ColorModel::Auto:
+ Q_UNREACHABLE_RETURN();
+ }
+
+ // If we also have a color specified, write it out.
+ if (!color.isValid())
+ return;
+
+ switch (actualColorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ *currentPage << color.redF()
+ << color.greenF()
+ << color.blueF();
+ break;
+ case QPdfEngine::ColorModel::Grayscale: {
+ const qreal gray = qGray(color.rgba()) / 255.;
+ *currentPage << gray;
+ break;
+ }
+ case QPdfEngine::ColorModel::CMYK:
+ *currentPage << color.cyanF()
+ << color.magentaF()
+ << color.yellowF()
+ << color.blackF();
+ break;
+ case QPdfEngine::ColorModel::Auto:
+ Q_UNREACHABLE_RETURN();
+ }
}
void QPdfEnginePrivate::writeInfo()
@@ -1697,7 +1774,8 @@ int QPdfEnginePrivate::writeXmpDcumentMetaData()
const QString metaDataDate = timeStr + tzStr;
QFile metaDataFile(":/qpdf/qpdfa_metadata.xml"_L1);
- metaDataFile.open(QIODevice::ReadOnly);
+ bool ok = metaDataFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(),
title.toHtmlEscaped(),
creator.toHtmlEscaped(),
@@ -1723,7 +1801,8 @@ int QPdfEnginePrivate::writeOutputIntent()
const int colorProfile = addXrefEntry(-1);
{
QFile colorProfileFile(":/qpdf/sRGB2014.icc"_L1);
- colorProfileFile.open(QIODevice::ReadOnly);
+ bool ok = colorProfileFile.open(QIODevice::ReadOnly);
+ Q_ASSERT(ok);
const QByteArray colorProfileData = colorProfileFile.readAll();
QByteArray data;
@@ -2078,12 +2157,18 @@ void QPdfEnginePrivate::writePage()
xprintf("<<\n"
"/ColorSpace <<\n"
"/PCSp %d 0 R\n"
+ "/PCSpg %d 0 R\n"
+ "/PCSpcmyk %d 0 R\n"
"/CSp /DeviceRGB\n"
"/CSpg /DeviceGray\n"
+ "/CSpcmyk /DeviceCMYK\n"
">>\n"
"/ExtGState <<\n"
"/GSa %d 0 R\n",
- patternColorSpace, graphicsState);
+ patternColorSpaceRGB,
+ patternColorSpaceGrayscale,
+ patternColorSpaceCMYK,
+ graphicsState);
for (int i = 0; i < currentPage->graphicStates.size(); ++i)
xprintf("/GState%d %d 0 R\n", currentPage->graphicStates.at(i), currentPage->graphicStates.at(i));
@@ -2336,7 +2421,7 @@ int QPdfEnginePrivate::writeCompressed(const char *src, int len)
return len;
}
-int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, int depth,
+int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct, bool isMono)
{
int image = addXrefEntry(-1);
@@ -2346,7 +2431,8 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
"/Width %d\n"
"/Height %d\n", width, height);
- if (depth == 1) {
+ switch (option) {
+ case WriteImageOption::Monochrome:
if (!isMono) {
xprintf("/ImageMask true\n"
"/Decode [1 0]\n");
@@ -2354,10 +2440,21 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height,
xprintf("/BitsPerComponent 1\n"
"/ColorSpace /DeviceGray\n");
}
- } else {
+ break;
+ case WriteImageOption::Grayscale:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceGray\n");
+ break;
+ case WriteImageOption::RGB:
xprintf("/BitsPerComponent 8\n"
- "/ColorSpace %s\n", (depth == 32) ? "/DeviceRGB" : "/DeviceGray");
+ "/ColorSpace /DeviceRGB\n");
+ break;
+ case WriteImageOption::CMYK:
+ xprintf("/BitsPerComponent 8\n"
+ "/ColorSpace /DeviceCMYK\n");
+ break;
}
+
if (maskObject > 0)
xprintf("/Mask %d 0 R\n", maskObject);
if (softMaskObject > 0)
@@ -2396,7 +2493,23 @@ struct QGradientBound {
};
Q_DECLARE_TYPEINFO(QGradientBound, Q_PRIMITIVE_TYPE);
-int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha)
+void QPdfEnginePrivate::ShadingFunctionResult::writeColorSpace(QPdf::ByteStream *stream) const
+{
+ *stream << "/ColorSpace ";
+ switch (colorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ *stream << "/DeviceRGB\n"; break;
+ case QPdfEngine::ColorModel::Grayscale:
+ *stream << "/DeviceGray\n"; break;
+ case QPdfEngine::ColorModel::CMYK:
+ *stream << "/DeviceCMYK\n"; break;
+ case QPdfEngine::ColorModel::Auto:
+ Q_UNREACHABLE(); break;
+ }
+}
+
+QPdfEnginePrivate::ShadingFunctionResult
+QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha)
{
QGradientStops stops = gradient->stops();
if (stops.isEmpty()) {
@@ -2408,6 +2521,35 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from
if (stops.at(stops.size() - 1).first < 1)
stops.append(QGradientStop(1, stops.at(stops.size() - 1).second));
+ // Color to use which colorspace to use
+ const QColor referenceColor = stops.constFirst().second;
+
+ switch (colorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ case QPdfEngine::ColorModel::Grayscale:
+ case QPdfEngine::ColorModel::CMYK:
+ break;
+ case QPdfEngine::ColorModel::Auto: {
+ // Make sure that all the stops have the same color spec
+ // (we don't support anything else)
+ const QColor::Spec referenceSpec = referenceColor.spec();
+ bool warned = false;
+ for (QGradientStop &stop : stops) {
+ if (stop.second.spec() != referenceSpec) {
+ if (!warned) {
+ qWarning("QPdfEngine: unable to create a gradient between colors of different spec");
+ warned = true;
+ }
+ stop.second = stop.second.convertTo(referenceSpec);
+ }
+ }
+ break;
+ }
+ }
+
+ ShadingFunctionResult result;
+ result.colorModel = colorModelForColor(referenceColor);
+
QList<int> functions;
const int numStops = stops.size();
functions.reserve(numStops - 1);
@@ -2423,8 +2565,31 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from
s << "/C0 [" << stops.at(i).second.alphaF() << "]\n"
"/C1 [" << stops.at(i + 1).second.alphaF() << "]\n";
} else {
- s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() << "]\n"
- "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() << "]\n";
+ switch (result.colorModel) {
+ case QPdfEngine::ColorModel::RGB:
+ s << "/C0 [" << stops.at(i).second.redF() << stops.at(i).second.greenF() << stops.at(i).second.blueF() << "]\n"
+ "/C1 [" << stops.at(i + 1).second.redF() << stops.at(i + 1).second.greenF() << stops.at(i + 1).second.blueF() << "]\n";
+ break;
+ case QPdfEngine::ColorModel::Grayscale:
+ s << "/C0 [" << (qGray(stops.at(i).second.rgba()) / 255.) << "]\n"
+ "/C1 [" << (qGray(stops.at(i + 1).second.rgba()) / 255.) << "]\n";
+ break;
+ case QPdfEngine::ColorModel::CMYK:
+ s << "/C0 [" << stops.at(i).second.cyanF()
+ << stops.at(i).second.magentaF()
+ << stops.at(i).second.yellowF()
+ << stops.at(i).second.blackF() << "]\n"
+ "/C1 [" << stops.at(i + 1).second.cyanF()
+ << stops.at(i + 1).second.magentaF()
+ << stops.at(i + 1).second.yellowF()
+ << stops.at(i + 1).second.blackF() << "]\n";
+ break;
+
+ case QPdfEngine::ColorModel::Auto:
+ Q_UNREACHABLE();
+ break;
+ }
+
}
s << ">>\n"
"endobj\n";
@@ -2492,7 +2657,8 @@ int QPdfEnginePrivate::createShadingFunction(const QGradient *gradient, int from
} else {
function = functions.at(0);
}
- return function;
+ result.function = function;
+ return result;
}
int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradient, const QTransform &matrix, bool alpha)
@@ -2538,17 +2704,22 @@ int QPdfEnginePrivate::generateLinearGradientShader(const QLinearGradient *gradi
}
}
- int function = createShadingFunction(gradient, from, to, reflect, alpha);
+ const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
QByteArray shader;
QPdf::ByteStream s(&shader);
s << "<<\n"
- "/ShadingType 2\n"
- "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
- "/AntiAlias true\n"
+ "/ShadingType 2\n";
+
+ if (alpha)
+ s << "/ColorSpace /DeviceGray\n";
+ else
+ shadingFunctionResult.writeColorSpace(&s);
+
+ s << "/AntiAlias true\n"
"/Coords [" << start.x() << start.y() << stop.x() << stop.y() << "]\n"
"/Extend [true true]\n"
- "/Function " << function << "0 R\n"
+ "/Function " << shadingFunctionResult.function << "0 R\n"
">>\n"
"endobj\n";
int shaderObject = addXrefEntry(-1);
@@ -2606,18 +2777,23 @@ int QPdfEnginePrivate::generateRadialGradientShader(const QRadialGradient *gradi
}
}
- int function = createShadingFunction(gradient, from, to, reflect, alpha);
+ const auto shadingFunctionResult = createShadingFunction(gradient, from, to, reflect, alpha);
QByteArray shader;
QPdf::ByteStream s(&shader);
s << "<<\n"
- "/ShadingType 3\n"
- "/ColorSpace " << (alpha ? "/DeviceGray\n" : "/DeviceRGB\n") <<
- "/AntiAlias true\n"
+ "/ShadingType 3\n";
+
+ if (alpha)
+ s << "/ColorSpace /DeviceGray\n";
+ else
+ shadingFunctionResult.writeColorSpace(&s);
+
+ s << "/AntiAlias true\n"
"/Domain [0 1]\n"
"/Coords [" << p0.x() << p0.y() << r0 << p1.x() << p1.y() << r1 << "]\n"
"/Extend [true true]\n"
- "/Function " << function << "0 R\n"
+ "/Function " << shadingFunctionResult.function << "0 R\n"
">>\n"
"endobj\n";
int shaderObject = addXrefEntry(-1);
@@ -2856,6 +3032,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
QImage image = img;
QImage::Format format = image.format();
+ const bool grayscale = (colorModel == QPdfEngine::ColorModel::Grayscale);
if (pdfVersion == QPdfEngine::Version_A1b) {
if (image.hasAlphaChannel()) {
@@ -2879,7 +3056,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
format = QImage::Format_Mono;
} else {
*bitmap = false;
- if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_CMYK8888) {
image = image.convertToFormat(QImage::Format_ARGB32);
format = QImage::Format_ARGB32;
}
@@ -2887,7 +3064,6 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
int w = image.width();
int h = image.height();
- int d = image.depth();
if (format == QImage::Format_Mono) {
int bytesPerLine = (w + 7) >> 3;
@@ -2898,7 +3074,7 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
memcpy(rawdata, image.constScanLine(y), bytesPerLine);
rawdata += bytesPerLine;
}
- object = writeImage(data, w, h, d, 0, 0, false, is_monochrome(img.colorTable()));
+ object = writeImage(data, w, h, WriteImageOption::Monochrome, 0, 0, false, is_monochrome(img.colorTable()));
} else {
QByteArray softMaskData;
bool dct = false;
@@ -2910,10 +3086,14 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
QBuffer buffer(&imageData);
QImageWriter writer(&buffer, "jpeg");
writer.setQuality(94);
+ if (format == QImage::Format_CMYK8888) {
+ // PDFs require CMYK colors not to be inverted in the JPEG encoding
+ writer.setSubType("CMYK");
+ }
writer.write(image);
dct = true;
- if (format != QImage::Format_RGB32) {
+ if (format != QImage::Format_RGB32 && format != QImage::Format_CMYK8888) {
softMaskData.resize(w * h);
uchar *sdata = (uchar *)softMaskData.data();
for (int y = 0; y < h; ++y) {
@@ -2928,41 +3108,59 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
}
} else {
- imageData.resize(grayscale ? w * h : 3 * w * h);
- uchar *data = (uchar *)imageData.data();
- softMaskData.resize(w * h);
- uchar *sdata = (uchar *)softMaskData.data();
- for (int y = 0; y < h; ++y) {
- const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (format == QImage::Format_CMYK8888) {
+ imageData.resize(grayscale ? w * h : w * h * 4);
+ uchar *data = (uchar *)imageData.data();
+ const qsizetype bytesPerLine = image.bytesPerLine();
if (grayscale) {
- for (int x = 0; x < w; ++x) {
- *(data++) = qGray(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ const uint *cmyk = (const uint *)image.constScanLine(y);
+ for (int x = 0; x < w; ++x)
+ *data++ = qGray(QCmyk32::fromCmyk32(*cmyk++).toColor().rgba());
}
} else {
- for (int x = 0; x < w; ++x) {
- *(data++) = qRed(*rgb);
- *(data++) = qGreen(*rgb);
- *(data++) = qBlue(*rgb);
- uchar alpha = qAlpha(*rgb);
- *sdata++ = alpha;
- hasMask |= (alpha < 255);
- hasAlpha |= (alpha != 0 && alpha != 255);
- ++rgb;
+ for (int y = 0; y < h; ++y) {
+ uchar *start = data + y * w * 4;
+ memcpy(start, image.constScanLine(y), bytesPerLine);
+ }
+ }
+ } else {
+ imageData.resize(grayscale ? w * h : 3 * w * h);
+ uchar *data = (uchar *)imageData.data();
+ softMaskData.resize(w * h);
+ uchar *sdata = (uchar *)softMaskData.data();
+ for (int y = 0; y < h; ++y) {
+ const QRgb *rgb = (const QRgb *)image.constScanLine(y);
+ if (grayscale) {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qGray(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
+ } else {
+ for (int x = 0; x < w; ++x) {
+ *(data++) = qRed(*rgb);
+ *(data++) = qGreen(*rgb);
+ *(data++) = qBlue(*rgb);
+ uchar alpha = qAlpha(*rgb);
+ *sdata++ = alpha;
+ hasMask |= (alpha < 255);
+ hasAlpha |= (alpha != 0 && alpha != 255);
+ ++rgb;
+ }
}
}
}
- if (format == QImage::Format_RGB32)
+ if (format == QImage::Format_RGB32 || format == QImage::Format_CMYK8888)
hasAlpha = hasMask = false;
}
int maskObject = 0;
int softMaskObject = 0;
if (hasAlpha) {
- softMaskObject = writeImage(softMaskData, w, h, 8, 0, 0);
+ softMaskObject = writeImage(softMaskData, w, h, WriteImageOption::Grayscale, 0, 0);
} else if (hasMask) {
// dither the soft mask to 1bit and add it. This also helps PDF viewers
// without transparency support
@@ -2978,9 +3176,18 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, bool lossless,
}
mdata += bytesPerLine;
}
- maskObject = writeImage(mask, w, h, 1, 0, 0);
+ maskObject = writeImage(mask, w, h, WriteImageOption::Monochrome, 0, 0);
}
- object = writeImage(imageData, w, h, grayscale ? 8 : 32,
+
+ const WriteImageOption option = [&]() {
+ if (grayscale)
+ return WriteImageOption::Grayscale;
+ if (format == QImage::Format_CMYK8888)
+ return WriteImageOption::CMYK;
+ return WriteImageOption::RGB;
+ }();
+
+ object = writeImage(imageData, w, h, option,
maskObject, softMaskObject, dct);
}
imageCache.insert(serial_no, object);
diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h
index 2c70ddf664..3c34e0bd7a 100644
--- a/src/gui/painting/qpdf_p.h
+++ b/src/gui/painting/qpdf_p.h
@@ -60,10 +60,6 @@ namespace QPdf {
static inline int maxMemorySize() { return 100000000; }
static inline int chunkSize() { return 10000000; }
- protected:
- void constructor_helper(QIODevice *dev);
- void constructor_helper(QByteArray *ba);
-
private:
void prepareBuffer();
@@ -142,7 +138,7 @@ public:
};
QPdfEngine();
- QPdfEngine(QPdfEnginePrivate &d);
+ explicit QPdfEngine(QPdfEnginePrivate &d);
~QPdfEngine() {}
void setOutputFilename(const QString &filename);
@@ -157,6 +153,18 @@ public:
void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType);
+ // keep in sync with QPdfWriter
+ enum class ColorModel
+ {
+ RGB,
+ Grayscale,
+ CMYK,
+ Auto,
+ };
+
+ ColorModel colorModel() const;
+ void setColorModel(ColorModel model);
+
// reimplementations QPaintEngine
bool begin(QPaintDevice *pdev) override;
bool end() override;
@@ -240,6 +248,7 @@ public:
bool needsTransform;
qreal opacity;
QPdfEngine::PdfVersion pdfVersion;
+ QPdfEngine::ColorModel colorModel;
QHash<QFontEngine::FaceId, QFontSubset *> fonts;
@@ -255,7 +264,6 @@ public:
QString creator;
bool embedFonts;
int resolution;
- bool grayscale;
// Page layout: size, orientation and margins
QPageLayout m_pageLayout;
@@ -265,8 +273,22 @@ private:
int generateGradientShader(const QGradient *gradient, const QTransform &matrix, bool alpha = false);
int generateLinearGradientShader(const QLinearGradient *lg, const QTransform &matrix, bool alpha);
int generateRadialGradientShader(const QRadialGradient *gradient, const QTransform &matrix, bool alpha);
- int createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha);
+ struct ShadingFunctionResult
+ {
+ int function;
+ QPdfEngine::ColorModel colorModel;
+ void writeColorSpace(QPdf::ByteStream *stream) const;
+ };
+ ShadingFunctionResult createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha);
+
+ enum class ColorDomain {
+ Stroking,
+ NonStroking,
+ NonStrokingPattern,
+ };
+ QPdfEngine::ColorModel colorModelForColor(const QColor &color) const;
+ void writeColor(ColorDomain domain, const QColor &color);
void writeInfo();
int writeXmpDcumentMetaData();
int writeOutputIntent();
@@ -282,7 +304,15 @@ private:
QDataStream* stream;
int streampos;
- int writeImage(const QByteArray &data, int width, int height, int depth,
+ enum class WriteImageOption
+ {
+ Monochrome,
+ Grayscale,
+ RGB,
+ CMYK,
+ };
+
+ int writeImage(const QByteArray &data, int width, int height, WriteImageOption option,
int maskObject, int softMaskObject, bool dct = false, bool isMono = false);
void writePage();
@@ -316,7 +346,10 @@ private:
// various PDF objects
int pageRoot, namesRoot, destsRoot, attachmentsRoot, catalog, info;
- int graphicsState, patternColorSpace;
+ int graphicsState;
+ int patternColorSpaceRGB;
+ int patternColorSpaceGrayscale;
+ int patternColorSpaceCMYK;
QList<uint> pages;
QHash<qint64, uint> imageCache;
QHash<QPair<uint, uint>, uint > alphaCache;
diff --git a/src/gui/painting/qpdfwriter.cpp b/src/gui/painting/qpdfwriter.cpp
index f28a460d7c..bce65927ab 100644
--- a/src/gui/painting/qpdfwriter.cpp
+++ b/src/gui/painting/qpdfwriter.cpp
@@ -295,6 +295,53 @@ bool QPdfWriter::newPage()
return d->engine->newPage();
}
+/*!
+ \enum QPdfWriter::ColorModel
+ \since 6.8
+
+ This enumeration describes the way in which the PDF engine interprets
+ stroking and filling colors, set as a QPainter's pen or brush (via
+ QPen and QBrush).
+
+ \value RGB All colors are converted to RGB and saved as such in the
+ PDF.
+
+ \value Grayscale All colors are converted to grayscale. For backwards
+ compatibility, they are emitted in the PDF output as RGB colors, with
+ identical quantities of red, green and blue.
+
+ \value CMYK All colors are converted to CMYK and saved as such.
+
+ \value Auto RGB colors are emitted as RGB; CMYK colors are emitted as
+ CMYK. Colors of any other color spec are converted to RGB.
+ This is the default since Qt 6.8.
+
+ \sa QColor, QGradient
+*/
+
+/*!
+ \since 6.8
+
+ Returns the color model used by this PDF writer.
+ The default is QPdfWriter::ColorModel::Auto.
+*/
+QPdfWriter::ColorModel QPdfWriter::colorModel() const
+{
+ Q_D(const QPdfWriter);
+ return static_cast<ColorModel>(d->engine->d_func()->colorModel);
+}
+
+/*!
+ \since 6.8
+
+ Sets the color model used by this PDF writer to \a model.
+*/
+void QPdfWriter::setColorModel(ColorModel model)
+{
+ Q_D(QPdfWriter);
+ d->engine->d_func()->colorModel = static_cast<QPdfEngine::ColorModel>(model);
+}
+
QT_END_NAMESPACE
#include "moc_qpdfwriter.cpp"
diff --git a/src/gui/painting/qpdfwriter.h b/src/gui/painting/qpdfwriter.h
index 5885c4ef1a..1a4b607b66 100644
--- a/src/gui/painting/qpdfwriter.h
+++ b/src/gui/painting/qpdfwriter.h
@@ -44,6 +44,18 @@ public:
void addFileAttachment(const QString &fileName, const QByteArray &data, const QString &mimeType = QString());
+ enum class ColorModel
+ {
+ RGB,
+ Grayscale,
+ CMYK,
+ Auto,
+ };
+ Q_ENUM(ColorModel)
+
+ ColorModel colorModel() const;
+ void setColorModel(ColorModel model);
+
protected:
QPaintEngine *paintEngine() const override;
int metric(PaintDeviceMetric id) const override;
diff --git a/src/gui/painting/qpen.cpp b/src/gui/painting/qpen.cpp
index 958539feda..d37beda6b6 100644
--- a/src/gui/painting/qpen.cpp
+++ b/src/gui/painting/qpen.cpp
@@ -10,8 +10,6 @@
QT_BEGIN_NAMESPACE
-typedef QPenPrivate QPenData;
-
/*!
\class QPen
\inmodule QtGui
@@ -193,7 +191,7 @@ typedef QPenPrivate QPenData;
*/
QPenPrivate::QPenPrivate(const QBrush &_brush, qreal _width, Qt::PenStyle penStyle,
Qt::PenCapStyle _capStyle, Qt::PenJoinStyle _joinStyle)
- : ref(1), dashOffset(0), miterLimit(2),
+ : dashOffset(0), miterLimit(2),
cosmetic(false)
{
width = _width;
@@ -209,17 +207,13 @@ static const Qt::PenJoinStyle qpen_default_join = Qt::BevelJoin;
class QPenDataHolder
{
public:
- QPenData *pen;
+ QPen::DataPtr pen;
QPenDataHolder(const QBrush &brush, qreal width, Qt::PenStyle penStyle,
Qt::PenCapStyle penCapStyle, Qt::PenJoinStyle _joinStyle)
- : pen(new QPenData(brush, width, penStyle, penCapStyle, _joinStyle))
+ : pen(new QPenPrivate(brush, width, penStyle, penCapStyle, _joinStyle))
{ }
- ~QPenDataHolder()
- {
- if (!pen->ref.deref())
- delete pen;
- pen = nullptr;
- }
+ ~QPenDataHolder() = default;
+ Q_DISABLE_COPY_MOVE(QPenDataHolder)
};
Q_GLOBAL_STATIC_WITH_ARGS(QPenDataHolder, defaultPenInstance,
@@ -234,7 +228,6 @@ Q_GLOBAL_STATIC_WITH_ARGS(QPenDataHolder, nullPenInstance,
QPen::QPen()
{
d = defaultPenInstance()->pen;
- d->ref.ref();
}
/*!
@@ -247,9 +240,8 @@ QPen::QPen(Qt::PenStyle style)
{
if (style == Qt::NoPen) {
d = nullPenInstance()->pen;
- d->ref.ref();
} else {
- d = new QPenData(Qt::black, 1, style, qpen_default_cap, qpen_default_join);
+ d = new QPenPrivate(Qt::black, 1, style, qpen_default_cap, qpen_default_join);
}
}
@@ -262,7 +254,7 @@ QPen::QPen(Qt::PenStyle style)
QPen::QPen(const QColor &color)
{
- d = new QPenData(color, 1, Qt::SolidLine, qpen_default_cap, qpen_default_join);
+ d = new QPenPrivate(color, 1, Qt::SolidLine, qpen_default_cap, qpen_default_join);
}
@@ -277,7 +269,7 @@ QPen::QPen(const QColor &color)
QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle s, Qt::PenCapStyle c, Qt::PenJoinStyle j)
{
- d = new QPenData(brush, width, s, c, j);
+ d = new QPenPrivate(brush, width, s, c, j);
}
/*!
@@ -287,10 +279,8 @@ QPen::QPen(const QBrush &brush, qreal width, Qt::PenStyle s, Qt::PenCapStyle c,
*/
QPen::QPen(const QPen &p) noexcept
+ : d(p.d)
{
- d = p.d;
- if (d)
- d->ref.ref();
}
@@ -309,11 +299,9 @@ QPen::QPen(const QPen &p) noexcept
Destroys the pen.
*/
-QPen::~QPen()
-{
- if (d && !d->ref.deref())
- delete d;
-}
+QPen::~QPen() = default;
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QPenPrivate)
/*!
\fn void QPen::detach()
@@ -327,14 +315,7 @@ QPen::~QPen()
void QPen::detach()
{
- if (d->ref.loadRelaxed() == 1)
- return;
-
- QPenData *x = new QPenData(*static_cast<QPenData *>(d));
- if (!d->ref.deref())
- delete d;
- x->ref.storeRelaxed(1);
- d = x;
+ d.detach();
}
@@ -407,9 +388,8 @@ void QPen::setStyle(Qt::PenStyle s)
return;
detach();
d->style = s;
- QPenData *dd = static_cast<QPenData *>(d);
- dd->dashPattern.clear();
- dd->dashOffset = 0;
+ d->dashPattern.clear();
+ d->dashOffset = 0;
}
/*!
@@ -419,36 +399,35 @@ void QPen::setStyle(Qt::PenStyle s)
*/
QList<qreal> QPen::dashPattern() const
{
- QPenData *dd = static_cast<QPenData *>(d);
if (d->style == Qt::SolidLine || d->style == Qt::NoPen) {
return QList<qreal>();
- } else if (dd->dashPattern.isEmpty()) {
+ } else if (d->dashPattern.isEmpty()) {
const qreal space = 2;
const qreal dot = 1;
const qreal dash = 4;
switch (d->style) {
case Qt::DashLine:
- dd->dashPattern.reserve(2);
- dd->dashPattern << dash << space;
+ d->dashPattern.reserve(2);
+ d->dashPattern << dash << space;
break;
case Qt::DotLine:
- dd->dashPattern.reserve(2);
- dd->dashPattern << dot << space;
+ d->dashPattern.reserve(2);
+ d->dashPattern << dot << space;
break;
case Qt::DashDotLine:
- dd->dashPattern.reserve(4);
- dd->dashPattern << dash << space << dot << space;
+ d->dashPattern.reserve(4);
+ d->dashPattern << dash << space << dot << space;
break;
case Qt::DashDotDotLine:
- dd->dashPattern.reserve(6);
- dd->dashPattern << dash << space << dot << space << dot << space;
+ d->dashPattern.reserve(6);
+ d->dashPattern << dash << space << dot << space << dot << space;
break;
default:
break;
}
}
- return dd->dashPattern;
+ return d->dashPattern;
}
/*!
@@ -487,13 +466,12 @@ void QPen::setDashPattern(const QList<qreal> &pattern)
return;
detach();
- QPenData *dd = static_cast<QPenData *>(d);
- dd->dashPattern = pattern;
+ d->dashPattern = pattern;
d->style = Qt::CustomDashLine;
- if ((dd->dashPattern.size() % 2) == 1) {
+ if ((d->dashPattern.size() % 2) == 1) {
qWarning("QPen::setDashPattern: Pattern not of even length");
- dd->dashPattern << 1;
+ d->dashPattern << 1;
}
}
@@ -505,8 +483,7 @@ void QPen::setDashPattern(const QList<qreal> &pattern)
*/
qreal QPen::dashOffset() const
{
- QPenData *dd = static_cast<QPenData *>(d);
- return dd->dashOffset;
+ return d->dashOffset;
}
/*!
Sets the dash offset (the starting point on the dash pattern) for this pen
@@ -528,13 +505,12 @@ qreal QPen::dashOffset() const
*/
void QPen::setDashOffset(qreal offset)
{
- if (qFuzzyCompare(offset, static_cast<QPenData *>(d)->dashOffset))
+ if (qFuzzyCompare(offset, d->dashOffset))
return;
detach();
- QPenData *dd = static_cast<QPenData *>(d);
- dd->dashOffset = offset;
+ d->dashOffset = offset;
if (d->style != Qt::CustomDashLine) {
- dd->dashPattern = dashPattern();
+ d->dashPattern = dashPattern();
d->style = Qt::CustomDashLine;
}
}
@@ -547,8 +523,7 @@ void QPen::setDashOffset(qreal offset)
*/
qreal QPen::miterLimit() const
{
- const QPenData *dd = static_cast<QPenData *>(d);
- return dd->miterLimit;
+ return d->miterLimit;
}
/*!
@@ -570,8 +545,7 @@ qreal QPen::miterLimit() const
void QPen::setMiterLimit(qreal limit)
{
detach();
- QPenData *dd = static_cast<QPenData *>(d);
- dd->miterLimit = limit;
+ d->miterLimit = limit;
}
@@ -782,8 +756,7 @@ bool QPen::isSolid() const
bool QPen::isCosmetic() const
{
- QPenData *dd = static_cast<QPenData *>(d);
- return (dd->cosmetic == true) || d->width == 0;
+ return (d->cosmetic == true) || d->width == 0;
}
@@ -797,8 +770,7 @@ bool QPen::isCosmetic() const
void QPen::setCosmetic(bool cosmetic)
{
detach();
- QPenData *dd = static_cast<QPenData *>(d);
- dd->cosmetic = cosmetic;
+ d->cosmetic = cosmetic;
}
@@ -825,19 +797,17 @@ void QPen::setCosmetic(bool cosmetic)
bool QPen::operator==(const QPen &p) const
{
- QPenData *dd = static_cast<QPenData *>(d);
- QPenData *pdd = static_cast<QPenData *>(p.d);
return (p.d == d)
|| (p.d->style == d->style
&& p.d->capStyle == d->capStyle
&& p.d->joinStyle == d->joinStyle
&& p.d->width == d->width
- && pdd->miterLimit == dd->miterLimit
+ && p.d->miterLimit == d->miterLimit
&& (d->style != Qt::CustomDashLine
- || (qFuzzyCompare(pdd->dashOffset, dd->dashOffset) &&
- pdd->dashPattern == dd->dashPattern))
+ || (qFuzzyCompare(p.d->dashOffset, d->dashOffset) &&
+ p.d->dashPattern == d->dashPattern))
&& p.d->brush == d->brush
- && pdd->cosmetic == dd->cosmetic);
+ && p.d->cosmetic == d->cosmetic);
}
@@ -869,14 +839,13 @@ bool QPen::isDetached()
QDataStream &operator<<(QDataStream &s, const QPen &p)
{
- QPenData *dd = static_cast<QPenData *>(p.d);
if (s.version() < 3) {
s << (quint8)p.style();
} else if (s.version() < QDataStream::Qt_4_3) {
s << (quint8)(uint(p.style()) | uint(p.capStyle()) | uint(p.joinStyle()));
} else {
s << (quint16)(uint(p.style()) | uint(p.capStyle()) | uint(p.joinStyle()));
- s << (bool)(dd->cosmetic);
+ s << (bool)(p.d->cosmetic);
}
if (s.version() < 7) {
@@ -965,16 +934,15 @@ QDataStream &operator>>(QDataStream &s, QPen &p)
}
p.detach();
- QPenData *dd = static_cast<QPenData *>(p.d);
- dd->width = width;
- dd->brush = brush;
- dd->style = Qt::PenStyle(style & Qt::MPenStyle);
- dd->capStyle = Qt::PenCapStyle(style & Qt::MPenCapStyle);
- dd->joinStyle = Qt::PenJoinStyle(style & Qt::MPenJoinStyle);
- dd->dashPattern = dashPattern;
- dd->miterLimit = miterLimit;
- dd->dashOffset = dashOffset;
- dd->cosmetic = cosmetic;
+ p.d->width = width;
+ p.d->brush = brush;
+ p.d->style = Qt::PenStyle(style & Qt::MPenStyle);
+ p.d->capStyle = Qt::PenCapStyle(style & Qt::MPenCapStyle);
+ p.d->joinStyle = Qt::PenJoinStyle(style & Qt::MPenJoinStyle);
+ p.d->dashPattern = dashPattern;
+ p.d->miterLimit = miterLimit;
+ p.d->dashOffset = dashOffset;
+ p.d->cosmetic = cosmetic;
return s;
}
diff --git a/src/gui/painting/qpen.h b/src/gui/painting/qpen.h
index 2f38098496..3367b96c35 100644
--- a/src/gui/painting/qpen.h
+++ b/src/gui/painting/qpen.h
@@ -4,6 +4,7 @@
#ifndef QPEN_H
#define QPEN_H
+#include <QtCore/qshareddata.h>
#include <QtGui/qtguiglobal.h>
#include <QtGui/qcolor.h>
#include <QtGui/qbrush.h>
@@ -21,6 +22,8 @@ Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPen &);
Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPen &);
#endif
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QPenPrivate, Q_GUI_EXPORT)
+
class Q_GUI_EXPORT QPen
{
public:
@@ -34,10 +37,9 @@ public:
~QPen();
QPen &operator=(const QPen &pen) noexcept;
- QPen(QPen &&other) noexcept
- : d(std::exchange(other.d, nullptr)) {}
+ QPen(QPen &&other) noexcept = default;
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QPen)
- void swap(QPen &other) noexcept { qt_ptr_swap(d, other.d); }
+ void swap(QPen &other) noexcept { d.swap(other.d); }
Qt::PenStyle style() const;
void setStyle(Qt::PenStyle);
@@ -79,15 +81,19 @@ public:
operator QVariant() const;
bool isDetached();
+
private:
friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, QPen &);
friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, const QPen &);
+public:
+ using DataPtr = QExplicitlySharedDataPointer<QPenPrivate>;
+
+private:
void detach();
- class QPenPrivate *d;
+ DataPtr d;
public:
- typedef QPenPrivate * DataPtr;
inline DataPtr &data_ptr() { return d; }
};
diff --git a/src/gui/painting/qpen_p.h b/src/gui/painting/qpen_p.h
index 39ed024d85..a939c5e4f6 100644
--- a/src/gui/painting/qpen_p.h
+++ b/src/gui/painting/qpen_p.h
@@ -16,14 +16,15 @@
//
#include <QtCore/private/qglobal_p.h>
+#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
-class QPenPrivate {
+class QPenPrivate : public QSharedData
+{
public:
QPenPrivate(const QBrush &brush, qreal width, Qt::PenStyle, Qt::PenCapStyle,
Qt::PenJoinStyle _joinStyle);
- QAtomicInt ref;
qreal width;
QBrush brush;
Qt::PenStyle style;
diff --git a/src/gui/painting/qpixellayout.cpp b/src/gui/painting/qpixellayout.cpp
index b196f48617..4f2f0ae13a 100644
--- a/src/gui/painting/qpixellayout.cpp
+++ b/src/gui/painting/qpixellayout.cpp
@@ -7,6 +7,7 @@
#include "qpixellayout_p.h"
#include "qrgba64_p.h"
#include <QtCore/private/qsimd_p.h>
+#include <QtGui/private/qcmyk_p.h>
QT_BEGIN_NAMESPACE
@@ -1187,10 +1188,12 @@ static const QRgba64 *QT_FASTCALL fetchRGBA64ToRGBA64PM(QRgba64 *buffer, const u
const QRgba64 *s = reinterpret_cast<const QRgba64 *>(src) + index;
#ifdef __SSE2__
for (int i = 0; i < count; ++i) {
+ const auto a = s[i].alpha();
__m128i vs = _mm_loadl_epi64((const __m128i *)(s + i));
__m128i va = _mm_shufflelo_epi16(vs, _MM_SHUFFLE(3, 3, 3, 3));
vs = multiplyAlpha65535(vs, va);
_mm_storel_epi64((__m128i *)(buffer + i), vs);
+ buffer[i].setAlpha(a);
}
#else
for (int i = 0; i < count; ++i)
@@ -1655,11 +1658,71 @@ static const QRgba64 *QT_FASTCALL fetchRGBA32FPMToRGBA64PM(QRgba64 *buffer, cons
return buffer;
}
+inline const uint *qt_convertCMYK8888ToARGB32PM(uint *buffer, const uint *src, int count)
+{
+ UNALIASED_CONVERSION_LOOP(buffer, src, count, [](uint s) {
+ const QColor color = QCmyk32::fromCmyk32(s).toColor();
+ return color.rgba();
+ });
+ return buffer;
+}
+
+static void QT_FASTCALL convertCMYK8888ToARGB32PM(uint *buffer, int count, const QList<QRgb> *)
+{
+ qt_convertCMYK8888ToARGB32PM(buffer, buffer, count);
+}
+
+static const QRgba64 *QT_FASTCALL convertCMYK8888ToToRGBA64PM(QRgba64 *buffer, const uint *src, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(src[i]).toColor().rgba64();
+ return buffer;
+}
+
+static const uint *QT_FASTCALL fetchCMYK8888ToARGB32PM(uint *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(s[i]).toColor().rgba();
+ return buffer;
+}
+
+static const QRgba64 *QT_FASTCALL fetchCMYK8888ToRGBA64PM(QRgba64 *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QCmyk32::fromCmyk32(s[i]).toColor().rgba64();
+ return buffer;
+}
+
+static void QT_FASTCALL storeCMYK8888FromARGB32PM(uchar *dest, const uint *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ QColor c = qUnpremultiply(src[i]);
+ d[i] = QCmyk32::fromColor(c).toUint();
+ }
+}
+
+static void QT_FASTCALL storeCMYK8888FromRGB32(uchar *dest, const uint *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ QColor c = src[i];
+ d[i] = QCmyk32::fromColor(c).toUint();
+ }
+}
+
// Note:
// convertToArgb32() assumes that no color channel is less than 4 bits.
// storeRGBFromARGB32PM() assumes that no color channel is more than 8 bits.
// QImage::rgbSwapped() assumes that the red and blue color channels have the same number of bits.
-QPixelLayout qPixelLayouts[QImage::NImageFormats] = {
+QPixelLayout qPixelLayouts[] = {
{ false, false, QPixelLayout::BPPNone, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }, // Format_Invalid
{ false, false, QPixelLayout::BPP1MSB, nullptr,
convertIndexedToARGB32PM, convertIndexedTo<QRgba64>,
@@ -1777,9 +1840,13 @@ QPixelLayout qPixelLayouts[QImage::NImageFormats] = {
convertPassThrough, nullptr,
fetchRGB32FToRGB32, fetchRGBA32FPMToRGBA64PM,
storeRGB32FFromRGB32, storeRGB32FFromRGB32 }, // Format_RGBA32FPx4_Premultiplied
+ { false, false, QPixelLayout::BPP32, nullptr,
+ convertCMYK8888ToARGB32PM, convertCMYK8888ToToRGBA64PM,
+ fetchCMYK8888ToARGB32PM, fetchCMYK8888ToRGBA64PM,
+ storeCMYK8888FromARGB32PM, storeCMYK8888FromRGB32 }, // Format_CMYK8888
};
-static_assert(sizeof(qPixelLayouts) / sizeof(*qPixelLayouts) == QImage::NImageFormats);
+static_assert(std::size(qPixelLayouts) == QImage::NImageFormats);
static void QT_FASTCALL convertFromRgb64(uint *dest, const QRgba64 *src, int length)
{
@@ -1914,7 +1981,15 @@ static void QT_FASTCALL storeRGBA32FPMFromRGBA64PM(uchar *dest, const QRgba64 *s
d[i] = qConvertRgb64ToRgbaF32(src[i]);
}
-ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[QImage::NImageFormats] = {
+static void QT_FASTCALL storeCMYKFromRGBA64PM(uchar *dest, const QRgba64 *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i)
+ d[i] = QCmyk32::fromColor(QColor(src[i])).toUint();
+}
+
+ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[] = {
nullptr,
nullptr,
nullptr,
@@ -1951,8 +2026,11 @@ ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[QImage::NImageFormats] = {
storeRGBX32FFromRGBA64PM,
storeRGBA32FFromRGBA64PM,
storeRGBA32FPMFromRGBA64PM,
+ storeCMYKFromRGBA64PM,
};
+static_assert(std::size(qStoreFromRGBA64PM) == QImage::NImageFormats);
+
#if QT_CONFIG(raster_fp)
static void QT_FASTCALL convertToRgbaF32(QRgbaFloat32 *dest, const uint *src, int length)
{
@@ -1998,7 +2076,16 @@ static const QRgbaFloat32 * QT_FASTCALL convertRGB30ToRGBA32F(QRgbaFloat32 *buff
return buffer;
}
-ConvertToFPFunc qConvertToRGBA32F[QImage::NImageFormats] = {
+static const QRgbaFloat32 * QT_FASTCALL convertCMYKToRGBA32F(QRgbaFloat32 *buffer, const uint *src, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QRgbaFloat32::fromArgb32(QCmyk32::fromCmyk32(src[i]).toColor().rgba());
+
+ return buffer;
+}
+
+ConvertToFPFunc qConvertToRGBA32F[] = {
nullptr,
convertIndexedTo<QRgbaFloat32>,
convertIndexedTo<QRgbaFloat32>,
@@ -2035,8 +2122,11 @@ ConvertToFPFunc qConvertToRGBA32F[QImage::NImageFormats] = {
nullptr,
nullptr,
nullptr,
+ convertCMYKToRGBA32F,
};
+static_assert(std::size(qConvertToRGBA32F) == QImage::NImageFormats);
+
static const QRgbaFloat32 *QT_FASTCALL fetchRGBX64ToRGBA32F(QRgbaFloat32 *buffer, const uchar *src, int index, int count,
const QList<QRgb> *, QDitherInfo *)
{
@@ -2101,7 +2191,17 @@ static const QRgbaFloat32 *QT_FASTCALL fetchRGBA32F(QRgbaFloat32 *, const uchar
return s;
}
-FetchAndConvertPixelsFuncFP qFetchToRGBA32F[QImage::NImageFormats] = {
+static const QRgbaFloat32 *QT_FASTCALL fetchCMYKToRGBA32F(QRgbaFloat32 *buffer, const uchar *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ const uint *s = reinterpret_cast<const uint *>(src) + index;
+ for (int i = 0; i < count; ++i)
+ buffer[i] = QRgbaFloat32::fromArgb32(QCmyk32::fromCmyk32(s[i]).toColor().rgba());
+
+ return buffer;
+}
+
+FetchAndConvertPixelsFuncFP qFetchToRGBA32F[] = {
nullptr,
fetchIndexedToRGBA32F<QPixelLayout::BPP1MSB>,
fetchIndexedToRGBA32F<QPixelLayout::BPP1LSB>,
@@ -2138,8 +2238,11 @@ FetchAndConvertPixelsFuncFP qFetchToRGBA32F[QImage::NImageFormats] = {
fetchRGBA32F,
fetchRGBA32FToRGBA32F,
fetchRGBA32F,
+ fetchCMYKToRGBA32F,
};
+static_assert(std::size(qFetchToRGBA32F) == QImage::NImageFormats);
+
static void QT_FASTCALL convertFromRgba32f(uint *dest, const QRgbaFloat32 *src, int length)
{
for (int i = 0; i < length; ++i)
@@ -2276,7 +2379,17 @@ static void QT_FASTCALL storeRGBA32FPMFromRGBA32F(uchar *dest, const QRgbaFloat3
}
}
-ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[QImage::NImageFormats] = {
+static void QT_FASTCALL storeCMYKFromRGBA32F(uchar *dest, const QRgbaFloat32 *src, int index, int count,
+ const QList<QRgb> *, QDitherInfo *)
+{
+ uint *d = reinterpret_cast<uint *>(dest) + index;
+ for (int i = 0; i < count; ++i) {
+ // Yikes, this really needs enablers in QColor and friends
+ d[i] = QCmyk32::fromColor(QColor(src[i].toArgb32())).toUint();
+ }
+}
+
+ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[] = {
nullptr,
nullptr,
nullptr,
@@ -2313,7 +2426,11 @@ ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[QImage::NImageFormats] = {
storeRGBX32FFromRGBA32F,
storeRGBA32FFromRGBA32F,
storeRGBA32FPMFromRGBA32F,
+ storeCMYKFromRGBA32F,
};
+
+static_assert(std::size(qStoreFromRGBA32F) == QImage::NImageFormats);
+
#endif // QT_CONFIG(raster_fp)
QT_END_NAMESPACE
diff --git a/src/gui/painting/qpixellayout_p.h b/src/gui/painting/qpixellayout_p.h
index 45f6c15365..14f19f4e74 100644
--- a/src/gui/painting/qpixellayout_p.h
+++ b/src/gui/painting/qpixellayout_p.h
@@ -321,12 +321,12 @@ struct QPixelLayout
extern ConvertAndStorePixelsFunc64 qStoreFromRGBA64PM[QImage::NImageFormats];
#if QT_CONFIG(raster_fp)
-extern ConvertToFPFunc qConvertToRGBA32F[QImage::NImageFormats];
-extern FetchAndConvertPixelsFuncFP qFetchToRGBA32F[QImage::NImageFormats];
-extern ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[QImage::NImageFormats];
+extern ConvertToFPFunc qConvertToRGBA32F[];
+extern FetchAndConvertPixelsFuncFP qFetchToRGBA32F[];
+extern ConvertAndStorePixelsFuncFP qStoreFromRGBA32F[];
#endif
-extern QPixelLayout qPixelLayouts[QImage::NImageFormats];
+extern QPixelLayout qPixelLayouts[];
extern MemRotateFunc qMemRotateFunctions[QPixelLayout::BPPCount][3];
diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
index d928af650a..1d55e9ab6a 100644
--- a/src/gui/painting/qplatformbackingstore.h
+++ b/src/gui/painting/qplatformbackingstore.h
@@ -36,7 +36,6 @@ class QPlatformGraphicsBuffer;
class QRhi;
class QRhiTexture;
class QRhiResourceUpdateBatch;
-class QRhiSwapChain;
struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
{
@@ -94,7 +93,8 @@ public:
enum Flag {
StacksOnTop = 0x01,
TextureIsSrgb = 0x02,
- NeedsPremultipliedAlphaBlending = 0x04
+ NeedsPremultipliedAlphaBlending = 0x04,
+ MirrorVertically = 0x08
};
Q_DECLARE_FLAGS(Flags, Flag)
@@ -173,7 +173,6 @@ public:
void setRhiConfig(const QPlatformBackingStoreRhiConfig &config);
QRhi *rhi() const;
- QRhiSwapChain *rhiSwapChain() const;
void surfaceAboutToBeDestroyed();
void graphicsDeviceReportedLost();
diff --git a/src/gui/painting/qpolygon.cpp b/src/gui/painting/qpolygon.cpp
index a3a89d7504..d615245eb4 100644
--- a/src/gui/painting/qpolygon.cpp
+++ b/src/gui/painting/qpolygon.cpp
@@ -419,12 +419,7 @@ QRect QPolygon::boundingRect() const
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QPolygon &a)
{
- QDebugStateSaver saver(dbg);
- dbg.nospace() << "QPolygon(";
- for (int i = 0; i < a.size(); ++i)
- dbg.nospace() << a.at(i);
- dbg.nospace() << ')';
- return dbg;
+ return QtPrivate::printSequentialContainer(dbg, "QPolygon", a);
}
#endif
@@ -702,13 +697,7 @@ QDataStream &operator>>(QDataStream &s, QPolygon &a)
QDataStream &operator<<(QDataStream &s, const QPolygonF &a)
{
- quint32 len = a.size();
- uint i;
-
- s << len;
- for (i = 0; i < len; ++i)
- s << a.at(i);
- return s;
+ return s << static_cast<const QList<QPointF> &>(a);
}
/*!
@@ -723,29 +712,14 @@ QDataStream &operator<<(QDataStream &s, const QPolygonF &a)
QDataStream &operator>>(QDataStream &s, QPolygonF &a)
{
- quint32 len;
- uint i;
-
- s >> len;
- a.reserve(a.size() + (int)len);
- QPointF p;
- for (i = 0; i < len; ++i) {
- s >> p;
- a.insert(i, p);
- }
- return s;
+ return s >> static_cast<QList<QPointF> &>(a);
}
#endif //QT_NO_DATASTREAM
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg, const QPolygonF &a)
{
- QDebugStateSaver saver(dbg);
- dbg.nospace() << "QPolygonF(";
- for (int i = 0; i < a.size(); ++i)
- dbg.nospace() << a.at(i);
- dbg.nospace() << ')';
- return dbg;
+ return QtPrivate::printSequentialContainer(dbg, "QPolygonF", a);
}
#endif
diff --git a/src/gui/painting/qrasterbackingstore.cpp b/src/gui/painting/qrasterbackingstore.cpp
index c0df8088d4..3b3ef2fd2e 100644
--- a/src/gui/painting/qrasterbackingstore.cpp
+++ b/src/gui/painting/qrasterbackingstore.cpp
@@ -67,7 +67,7 @@ void QRasterBackingStore::beginPaint(const QRegion &region)
if (m_image.devicePixelRatio() != nativeWindowDevicePixelRatio || m_image.size() != effectiveBufferSize) {
m_image = QImage(effectiveBufferSize, format());
m_image.setDevicePixelRatio(nativeWindowDevicePixelRatio);
- if (m_image.format() == QImage::Format_ARGB32_Premultiplied)
+ if (m_image.hasAlphaChannel())
m_image.fill(Qt::transparent);
}
diff --git a/src/gui/painting/qregion.cpp b/src/gui/painting/qregion.cpp
index 94f1f31e6a..f9089d7bba 100644
--- a/src/gui/painting/qregion.cpp
+++ b/src/gui/painting/qregion.cpp
@@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE
contains() a QPoint or QRect. The bounding rectangle can be found
with boundingRect().
- Iteration over the region (with begin(), end(), or C++11
+ Iteration over the region (with begin(), end(), or
ranged-for loops) gives a decomposition of the region into
rectangles.
@@ -876,9 +876,15 @@ QRegion QRegion::intersect(const QRect &r) const
/*!
\fn void QRegion::setRects(const QRect *rects, int number)
+ \overload
+ \obsolete Use the QSpan overload instead.
+*/
+
+/*!
+ \fn void QRegion::setRects(QSpan<const QRect> rects)
+ \since 6.8
- Sets the region using the array of rectangles specified by \a rects and
- \a number.
+ Sets the region using the array of rectangles specified by \a rects.
The rectangles \e must be optimally Y-X sorted and follow these restrictions:
\list
@@ -892,6 +898,11 @@ QRegion QRegion::intersect(const QRect &r) const
\omit
Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos).
\endomit
+
+ \note For historical reasons, \c{rects.size()} must be less than \c{INT_MAX}
+ (see rectCount()).
+
+ \sa rects()
*/
namespace {
@@ -4214,18 +4225,39 @@ QRegion::const_iterator QRegion::end() const noexcept
return d->qt_rgn ? d->qt_rgn->end() : nullptr;
}
-void QRegion::setRects(const QRect *rects, int num)
+static Q_DECL_COLD_FUNCTION
+void set_rects_warn(const char *what)
+{
+ qWarning("QRegion::setRects(): %s", what);
+}
+
+void QRegion::setRects(const QRect *r, int n)
{
+ if (!r && n) { // old setRects() allowed this, but QSpan doesn't
+ set_rects_warn("passing num != 0 when rects == nullptr is deprecated.");
+ n = 0;
+ }
+ setRects(QSpan<const QRect>(r, n));
+}
+
+void QRegion::setRects(QSpan<const QRect> rects)
+{
+ const auto num = int(rects.size());
+ if (num != rects.size()) {
+ set_rects_warn("span size exceeds INT_MAX, ignoring");
+ return;
+ }
+
*this = QRegion();
- if (!rects || num == 0 || (num == 1 && rects->isEmpty()))
+ if (!rects.data() || num == 0 || (num == 1 && rects.front().isEmpty()))
return;
detach();
d->qt_rgn->numRects = num;
if (num == 1) {
- d->qt_rgn->extents = *rects;
- d->qt_rgn->innerRect = *rects;
+ d->qt_rgn->extents = rects.front();
+ d->qt_rgn->innerRect = rects.front();
} else {
d->qt_rgn->rects.resize(num);
@@ -4246,12 +4278,30 @@ void QRegion::setRects(const QRect *rects, int num)
}
}
+/*!
+ \since 6.8
+
+ Returns a span of non-overlapping rectangles that make up the region. The
+ span remains valid until the next call of a mutating (non-const) method on
+ this region.
+
+ The union of all the rectangles is equal to the original region.
+
+ \note This functions existed in Qt 5, too, but returned QVector<QRect>
+ instead.
+
+ \sa setRects()
+*/
+QSpan<const QRect> QRegion::rects() const noexcept
+{
+ return {begin(), end()};
+};
+
int QRegion::rectCount() const noexcept
{
return (d->qt_rgn ? d->qt_rgn->numRects : 0);
}
-
bool QRegion::operator==(const QRegion &r) const
{
if (!d->qt_rgn)
diff --git a/src/gui/painting/qregion.h b/src/gui/painting/qregion.h
index b0051b6067..4b852815f3 100644
--- a/src/gui/painting/qregion.h
+++ b/src/gui/painting/qregion.h
@@ -8,11 +8,11 @@
#include <QtCore/qatomic.h>
#include <QtCore/qrect.h>
#include <QtGui/qwindowdefs.h>
-#include <QtCore/qcontainerfwd.h>
#ifndef QT_NO_DATASTREAM
#include <QtCore/qdatastream.h>
#endif
+#include <QtCore/qspan.h>
QT_BEGIN_NAMESPACE
@@ -75,6 +75,8 @@ public:
QRect boundingRect() const noexcept;
void setRects(const QRect *rect, int num);
+ void setRects(QSpan<const QRect> r);
+ QSpan<const QRect> rects() const noexcept;
int rectCount() const noexcept;
QRegion operator|(const QRegion &r) const;
diff --git a/src/gui/painting/qrgbafloat.h b/src/gui/painting/qrgbafloat.h
index 160623eb1e..da74328f71 100644
--- a/src/gui/painting/qrgbafloat.h
+++ b/src/gui/painting/qrgbafloat.h
@@ -61,19 +61,19 @@ public:
constexpr bool isOpaque() const { return a >= FastType(1.0f); }
constexpr bool isTransparent() const { return a <= FastType(0.0f); }
- constexpr FastType red() const { return r; }
- constexpr FastType green() const { return g; }
- constexpr FastType blue() const { return b; }
- constexpr FastType alpha() const { return a; }
- void setRed(FastType _red) { r = F(_red); }
- void setGreen(FastType _green) { g = F(_green); }
- void setBlue(FastType _blue) { b = F(_blue); }
- void setAlpha(FastType _alpha) { a = F(_alpha); }
-
- constexpr FastType redNormalized() const { return clamp01(r); }
- constexpr FastType greenNormalized() const { return clamp01(g); }
- constexpr FastType blueNormalized() const { return clamp01(b); }
- constexpr FastType alphaNormalized() const { return clamp01(a); }
+ constexpr float red() const { return r; }
+ constexpr float green() const { return g; }
+ constexpr float blue() const { return b; }
+ constexpr float alpha() const { return a; }
+ void setRed(float _red) { r = F(_red); }
+ void setGreen(float _green) { g = F(_green); }
+ void setBlue(float _blue) { b = F(_blue); }
+ void setAlpha(float _alpha) { a = F(_alpha); }
+
+ constexpr float redNormalized() const { return clamp01(r); }
+ constexpr float greenNormalized() const { return clamp01(g); }
+ constexpr float blueNormalized() const { return clamp01(b); }
+ constexpr float alphaNormalized() const { return clamp01(a); }
constexpr quint8 red8() const { return qRound(redNormalized() * FastType(255.0f)); }
constexpr quint8 green8() const { return qRound(greenNormalized() * FastType(255.0f)); }
diff --git a/src/gui/painting/qrgbafloat.qdoc b/src/gui/painting/qrgbafloat.qdoc
index f41064df38..3ec0d209f4 100644
--- a/src/gui/painting/qrgbafloat.qdoc
+++ b/src/gui/painting/qrgbafloat.qdoc
@@ -33,7 +33,7 @@
*/
/*!
- \fn template<typename F> QRgbaFloat<F>::fromRgba64(quint16 r, quint16 g, quint16 b, quint16 a)
+ \fn template<typename F> QRgbaFloat QRgbaFloat<F>::fromRgba64(quint16 red, quint16 green, quint16 blue, quint16 alpha)
Constructs a QRgbaFloat value from the four 16-bit integer color channels \a red, \a green, \a blue and \a alpha.
@@ -41,7 +41,7 @@
*/
/*!
- \fn template<typename F> QRgbaFloat<F>::fromRgba(quint8 red, quint8 green, quint8 blue, quint8 alpha)
+ \fn template<typename F> QRgbaFloat QRgbaFloat<F>::fromRgba(quint8 red, quint8 green, quint8 blue, quint8 alpha)
Constructs a QRgbaFloat value from the four 8-bit color channels \a red, \a green, \a blue and \a alpha.
@@ -49,7 +49,7 @@
*/
/*!
- \fn template<typename F> QRgbaFloat<F>::fromArgb32(uint rgb)
+ \fn template<typename F> QRgbaFloat QRgbaFloat<F>::fromArgb32(uint rgb)
Constructs a QRgbaFloat value from the 32bit ARGB value \a rgb.
@@ -81,7 +81,7 @@
*/
/*!
- \fn template<typename F> void QRgbaFloat<F>::setRed(QRgbaFloat::FastType red)
+ \fn template<typename F> void QRgbaFloat<F>::setRed(float red)
Sets the red color component of this color to \a red.
@@ -97,7 +97,7 @@
*/
/*!
- \fn template<typename F> void QRgbaFloat<F>::setGreen(QRgbaFloat::FastType green)
+ \fn template<typename F> void QRgbaFloat<F>::setGreen(float green)
Sets the green color component of this color to \a green.
@@ -113,7 +113,7 @@
*/
/*!
- \fn template<typename F> void QRgbaFloat<F>::setBlue(QRgbaFloat::FastType blue)
+ \fn template<typename F> void QRgbaFloat<F>::setBlue(float blue)
Sets the blue color component of this color to \a blue.
@@ -129,7 +129,7 @@
*/
/*!
- \fn template<typename F> void QRgbaFloat<F>::setAlpha(QRgbaFloat::FastType alpha)
+ \fn template<typename F> void QRgbaFloat<F>::setAlpha(float alpha)
Sets the alpha of this color to \a alpha.
diff --git a/src/gui/painting/qrhibackingstore.cpp b/src/gui/painting/qrhibackingstore.cpp
index fd7045e3f6..586dfb44a4 100644
--- a/src/gui/painting/qrhibackingstore.cpp
+++ b/src/gui/painting/qrhibackingstore.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qrhibackingstore_p.h"
+#include <private/qimage_p.h>
QT_BEGIN_NAMESPACE
@@ -14,17 +15,24 @@ QRhiBackingStore::~QRhiBackingStore()
{
}
-void QRhiBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
+void QRhiBackingStore::flush(QWindow *flushedWindow, const QRegion &region, const QPoint &offset)
{
Q_UNUSED(region);
Q_UNUSED(offset);
- if (window != this->window())
+ if (flushedWindow->surfaceType() != window()->surfaceType()) {
+ qWarning() << "Cannot flush child window" << flushedWindow
+ << "with surface type" << flushedWindow->surfaceType() << ";"
+ << "Must match" << window()->surfaceType() << "of" << window();
+
+ // FIXME: Support different surface types by not tying the
+ // RHI config to the backing store itself (per window config).
return;
+ }
if (!rhi()) {
QPlatformBackingStoreRhiConfig rhiConfig;
- switch (window->surfaceType()) {
+ switch (window()->surfaceType()) {
case QSurface::OpenGLSurface:
rhiConfig.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
break;
@@ -40,7 +48,21 @@ void QRhiBackingStore::flush(QWindow *window, const QRegion &region, const QPoin
static QPlatformTextureList emptyTextureList;
bool translucentBackground = m_image.hasAlphaChannel();
- rhiFlush(window, window->devicePixelRatio(), region, offset, &emptyTextureList, translucentBackground);
+ rhiFlush(flushedWindow, flushedWindow->devicePixelRatio(),
+ region, offset, &emptyTextureList, translucentBackground);
+}
+
+QImage::Format QRhiBackingStore::format() const
+{
+ QImage::Format fmt = QRasterBackingStore::format();
+
+ // With render-to-texture widgets and QRhi-based flushing the backingstore
+ // image must have an alpha channel. Hence upgrading the format. Matches
+ // what other platforms (Windows, xcb) do.
+ if (QImage::toPixelFormat(fmt).alphaUsage() != QPixelFormat::UsesAlpha)
+ fmt = qt_maybeDataCompatibleAlphaVersion(fmt);
+
+ return fmt;
}
QT_END_NAMESPACE
diff --git a/src/gui/painting/qrhibackingstore_p.h b/src/gui/painting/qrhibackingstore_p.h
index 95778fa74c..f222db860f 100644
--- a/src/gui/painting/qrhibackingstore_p.h
+++ b/src/gui/painting/qrhibackingstore_p.h
@@ -26,6 +26,7 @@ public:
~QRhiBackingStore();
void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
+ QImage::Format format() const override;
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qt_attribution.json b/src/gui/painting/qt_attribution.json
index d0f2468e6a..33ed2fd5c7 100644
--- a/src/gui/painting/qt_attribution.json
+++ b/src/gui/painting/qt_attribution.json
@@ -9,7 +9,7 @@
"Description": "FreeType is a freely available software library to render fonts.",
"Homepage": "http://www.freetype.org",
"License": "Freetype Project License or GNU General Public License v2.0 only",
- "LicenseId": "FTL or GPL-2.0",
+ "LicenseId": "FTL OR GPL-2.0-only",
"LicenseFile": "../../3rdparty/freetype/LICENSE.txt",
"Copyright": "Copyright 2000-2016 by David Turner, Robert Wilhelm, and Werner Lemberg."
},
@@ -24,9 +24,9 @@
"LicenseId": "BSD-2-Clause AND Imlib2",
"License": "BSD 2-clause \"Simplified\" License and Imlib2 License",
"LicenseFile": "QIMAGETRANSFORM_LICENSE.txt",
- "Copyright": "Copyright (C) 2004, 2005 Daniel M. Duley.
- (C) Carsten Haitzler and various contributors.
- (C) Willem Monsuwe <willem@stack.nl>"
+ "Copyright": ["Copyright (C) 2004, 2005 Daniel M. Duley.",
+ "(C) Carsten Haitzler and various contributors.",
+ "(C) Willem Monsuwe <willem@stack.nl>"]
},
{
"Id": "xserverhelper",
@@ -40,7 +40,7 @@
"License": "X11 License and Historical Permission Notice and Disclaimer",
"LicenseId": "X11 AND HPND",
"LicenseFile": "XCONSORTIUM_LICENSE.txt",
- "Copyright": "Copyright (c) 1987, 1988 X Consortium
-Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts."
+ "Copyright": ["Copyright (c) 1987, 1988 X Consortium",
+ "Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts."]
}
]
diff --git a/src/gui/painting/qtransform.cpp b/src/gui/painting/qtransform.cpp
index b2720b27ae..df57d2c190 100644
--- a/src/gui/painting/qtransform.cpp
+++ b/src/gui/painting/qtransform.cpp
@@ -54,7 +54,7 @@ static void nanWarning(const char *func)
if (t == TxProject) { \
qreal w = (m_matrix[0][2] * FX_ + m_matrix[1][2] * FY_ + m_matrix[2][2]); \
if (w < qreal(Q_NEAR_CLIP)) w = qreal(Q_NEAR_CLIP); \
- w = 1./w; \
+ w = qreal(1.)/w; \
nx *= w; \
ny *= w; \
} \
@@ -1145,30 +1145,8 @@ QPoint QTransform::map(const QPoint &p) const
qreal x = 0, y = 0;
TransformationType t = inline_type();
- switch(t) {
- case TxNone:
- x = fx;
- y = fy;
- break;
- case TxTranslate:
- x = fx + m_matrix[2][0];
- y = fy + m_matrix[2][1];
- break;
- case TxScale:
- x = m_matrix[0][0] * fx + m_matrix[2][0];
- y = m_matrix[1][1] * fy + m_matrix[2][1];
- break;
- case TxRotate:
- case TxShear:
- case TxProject:
- x = m_matrix[0][0] * fx + m_matrix[1][0] * fy + m_matrix[2][0];
- y = m_matrix[0][1] * fx + m_matrix[1][1] * fy + m_matrix[2][1];
- if (t == TxProject) {
- qreal w = 1./(m_matrix[0][2] * fx + m_matrix[1][2] * fy + m_matrix[2][2]);
- x *= w;
- y *= w;
- }
- }
+ MAP(fx, fy, x, y);
+
return QPoint(qRound(x), qRound(y));
}
@@ -1196,30 +1174,8 @@ QPointF QTransform::map(const QPointF &p) const
qreal x = 0, y = 0;
TransformationType t = inline_type();
- switch(t) {
- case TxNone:
- x = fx;
- y = fy;
- break;
- case TxTranslate:
- x = fx + m_matrix[2][0];
- y = fy + m_matrix[2][1];
- break;
- case TxScale:
- x = m_matrix[0][0] * fx + m_matrix[2][0];
- y = m_matrix[1][1] * fy + m_matrix[2][1];
- break;
- case TxRotate:
- case TxShear:
- case TxProject:
- x = m_matrix[0][0] * fx + m_matrix[1][0] * fy + m_matrix[2][0];
- y = m_matrix[0][1] * fx + m_matrix[1][1] * fy + m_matrix[2][1];
- if (t == TxProject) {
- qreal w = 1./(m_matrix[0][2] * fx + m_matrix[1][2] * fy + m_matrix[2][2]);
- x *= w;
- y *= w;
- }
- }
+ MAP(fx, fy, x, y);
+
return QPointF(x, y);
}
@@ -1267,41 +1223,9 @@ QLine QTransform::map(const QLine &l) const
qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0;
TransformationType t = inline_type();
- switch(t) {
- case TxNone:
- x1 = fx1;
- y1 = fy1;
- x2 = fx2;
- y2 = fy2;
- break;
- case TxTranslate:
- x1 = fx1 + m_matrix[2][0];
- y1 = fy1 + m_matrix[2][1];
- x2 = fx2 + m_matrix[2][0];
- y2 = fy2 + m_matrix[2][1];
- break;
- case TxScale:
- x1 = m_matrix[0][0] * fx1 + m_matrix[2][0];
- y1 = m_matrix[1][1] * fy1 + m_matrix[2][1];
- x2 = m_matrix[0][0] * fx2 + m_matrix[2][0];
- y2 = m_matrix[1][1] * fy2 + m_matrix[2][1];
- break;
- case TxRotate:
- case TxShear:
- case TxProject:
- x1 = m_matrix[0][0] * fx1 + m_matrix[1][0] * fy1 + m_matrix[2][0];
- y1 = m_matrix[0][1] * fx1 + m_matrix[1][1] * fy1 + m_matrix[2][1];
- x2 = m_matrix[0][0] * fx2 + m_matrix[1][0] * fy2 + m_matrix[2][0];
- y2 = m_matrix[0][1] * fx2 + m_matrix[1][1] * fy2 + m_matrix[2][1];
- if (t == TxProject) {
- qreal w = 1./(m_matrix[0][2] * fx1 + m_matrix[1][2] * fy1 + m_matrix[2][2]);
- x1 *= w;
- y1 *= w;
- w = 1./(m_matrix[0][2] * fx2 + m_matrix[1][2] * fy2 + m_matrix[2][2]);
- x2 *= w;
- y2 *= w;
- }
- }
+ MAP(fx1, fy1, x1, y1);
+ MAP(fx2, fy2, x2, y2);
+
return QLine(qRound(x1), qRound(y1), qRound(x2), qRound(y2));
}
@@ -1326,66 +1250,12 @@ QLineF QTransform::map(const QLineF &l) const
qreal x1 = 0, y1 = 0, x2 = 0, y2 = 0;
TransformationType t = inline_type();
- switch(t) {
- case TxNone:
- x1 = fx1;
- y1 = fy1;
- x2 = fx2;
- y2 = fy2;
- break;
- case TxTranslate:
- x1 = fx1 + m_matrix[2][0];
- y1 = fy1 + m_matrix[2][1];
- x2 = fx2 + m_matrix[2][0];
- y2 = fy2 + m_matrix[2][1];
- break;
- case TxScale:
- x1 = m_matrix[0][0] * fx1 + m_matrix[2][0];
- y1 = m_matrix[1][1] * fy1 + m_matrix[2][1];
- x2 = m_matrix[0][0] * fx2 + m_matrix[2][0];
- y2 = m_matrix[1][1] * fy2 + m_matrix[2][1];
- break;
- case TxRotate:
- case TxShear:
- case TxProject:
- x1 = m_matrix[0][0] * fx1 + m_matrix[1][0] * fy1 + m_matrix[2][0];
- y1 = m_matrix[0][1] * fx1 + m_matrix[1][1] * fy1 + m_matrix[2][1];
- x2 = m_matrix[0][0] * fx2 + m_matrix[1][0] * fy2 + m_matrix[2][0];
- y2 = m_matrix[0][1] * fx2 + m_matrix[1][1] * fy2 + m_matrix[2][1];
- if (t == TxProject) {
- qreal w = 1./(m_matrix[0][2] * fx1 + m_matrix[1][2] * fy1 + m_matrix[2][2]);
- x1 *= w;
- y1 *= w;
- w = 1./(m_matrix[0][2] * fx2 + m_matrix[1][2] * fy2 + m_matrix[2][2]);
- x2 *= w;
- y2 *= w;
- }
- }
- return QLineF(x1, y1, x2, y2);
-}
+ MAP(fx1, fy1, x1, y1);
+ MAP(fx2, fy2, x2, y2);
-static QPolygonF mapProjective(const QTransform &transform, const QPolygonF &poly)
-{
- if (poly.size() == 0)
- return poly;
-
- if (poly.size() == 1)
- return QPolygonF() << transform.map(poly.at(0));
-
- QPainterPath path;
- path.addPolygon(poly);
-
- path = transform.map(path);
-
- QPolygonF result;
- const int elementCount = path.elementCount();
- result.reserve(elementCount);
- for (int i = 0; i < elementCount; ++i)
- result << path.elementAt(i);
- return result;
+ return QLineF(x1, y1, x2, y2);
}
-
/*!
\fn QPolygonF operator *(const QPolygonF &polygon, const QTransform &matrix)
\since 4.3
@@ -1419,9 +1289,6 @@ QPolygonF QTransform::map(const QPolygonF &a) const
if (t <= TxTranslate)
return a.translated(m_matrix[2][0], m_matrix[2][1]);
- if (t >= QTransform::TxProject)
- return mapProjective(*this, a);
-
int size = a.size();
int i;
QPolygonF p(size);
@@ -1449,9 +1316,6 @@ QPolygon QTransform::map(const QPolygon &a) const
if (t <= TxTranslate)
return a.translated(qRound(m_matrix[2][0]), qRound(m_matrix[2][1]));
- if (t >= QTransform::TxProject)
- return mapProjective(*this, QPolygonF(a)).toPolygon();
-
int size = a.size();
int i;
QPolygon p(size);
@@ -1882,14 +1746,6 @@ void QTransform::setMatrix(qreal m11, qreal m12, qreal m13,
m_dirty = TxProject;
}
-static inline bool needsPerspectiveClipping(const QRectF &rect, const QTransform &transform)
-{
- const qreal wx = qMin(transform.m13() * rect.left(), transform.m13() * rect.right());
- const qreal wy = qMin(transform.m23() * rect.top(), transform.m23() * rect.bottom());
-
- return wx + wy + transform.m33() < Q_NEAR_CLIP;
-}
-
QRect QTransform::mapRect(const QRect &rect) const
{
TransformationType t = inline_type();
@@ -1910,8 +1766,7 @@ QRect QTransform::mapRect(const QRect &rect) const
y -= h;
}
return QRect(x, y, w, h);
- } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) {
- // see mapToPolygon for explanations of the algorithm.
+ } else {
qreal x = 0, y = 0;
MAP(rect.left(), rect.top(), x, y);
qreal xmin = x;
@@ -1933,11 +1788,7 @@ QRect QTransform::mapRect(const QRect &rect) const
ymin = qMin(ymin, y);
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
- return QRect(qRound(xmin), qRound(ymin), qRound(xmax)-qRound(xmin), qRound(ymax)-qRound(ymin));
- } else {
- QPainterPath path;
- path.addRect(rect);
- return map(path).boundingRect().toRect();
+ return QRectF(xmin, ymin, xmax-xmin, ymax-ymin).toRect();
}
}
@@ -1980,7 +1831,7 @@ QRectF QTransform::mapRect(const QRectF &rect) const
y -= h;
}
return QRectF(x, y, w, h);
- } else if (t < TxProject || !needsPerspectiveClipping(rect, *this)) {
+ } else {
qreal x = 0, y = 0;
MAP(rect.x(), rect.y(), x, y);
qreal xmin = x;
@@ -2003,10 +1854,6 @@ QRectF QTransform::mapRect(const QRectF &rect) const
xmax = qMax(xmax, x);
ymax = qMax(ymax, y);
return QRectF(xmin, ymin, xmax-xmin, ymax - ymin);
- } else {
- QPainterPath path;
- path.addRect(rect);
- return map(path).boundingRect();
}
}
diff --git a/src/gui/platform/android/qandroidnativeinterface.cpp b/src/gui/platform/android/qandroidnativeinterface.cpp
index 1bc718cbf1..c1c4b7149f 100644
--- a/src/gui/platform/android/qandroidnativeinterface.cpp
+++ b/src/gui/platform/android/qandroidnativeinterface.cpp
@@ -35,6 +35,20 @@ QOffscreenSurface *QNativeInterface::QAndroidOffscreenSurface::fromNative(ANati
&QAndroidOffScreenIntegration::createOffscreenSurface>(nativeSurface);
}
-QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QAndroidScreen);
+/*!
+ \class QNativeInterface::QAndroidScreen
+ \since 6.7
+ \brief Native interface to a screen.
+
+ Accessed through QScreen::nativeInterface().
+ \inmodule QtGui
+ \ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
+*/
+/*!
+ \fn int QNativeInterface::QAndroidScreen::displayId() const;
+ \return the id of the underlying Android display.
+*/
+QT_DEFINE_NATIVE_INTERFACE(QAndroidScreen);
QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm
new file mode 100644
index 0000000000..7e0ed184dc
--- /dev/null
+++ b/src/gui/platform/darwin/qappleiconengine.mm
@@ -0,0 +1,464 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qappleiconengine_p.h"
+
+#if defined(Q_OS_MACOS)
+# include <AppKit/AppKit.h>
+#elif defined(QT_PLATFORM_UIKIT)
+# include <UIKit/UIKit.h>
+#endif
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+#include <QtGui/qstylehints.h>
+
+#include <QtGui/private/qcoregraphics_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace {
+auto *loadImage(const QString &iconName)
+{
+ static constexpr std::pair<QLatin1StringView, NSString *> iconMap[] = {
+ {"address-book-new"_L1, @"book.closed"},
+ {"application-exit"_L1, @"xmark.circle"},
+ {"appointment-new"_L1, @"calendar.badge.plus"},
+ {"call-start"_L1, @"phone.arrow.up.right"},
+ {"call-stop"_L1, @"phone.down"},
+ {"contact-new"_L1, @"person.crop.circle.badge.plus"},
+ {"document-new"_L1, @"doc.badge.plus"},
+ {"document-open"_L1, @"folder"},
+ {"document-open-recent"_L1, @"doc.badge.clock"},
+ {"document-page-setup"_L1, @"doc.badge.gearshape"},
+ {"document-print"_L1, @"printer"},
+ //{"document-print-preview"_L1, @""},
+ {"document-properties"_L1, @"doc.badge.ellipsis"},
+ //{"document-revert"_L1, @""},
+ {"document-save"_L1, @"square.and.arrow.down"},
+ //{"document-save-as"_L1, @""},
+ {"document-send"_L1, @"paperplane"},
+ {"edit-clear"_L1, @"xmark.circle"},
+ {"edit-copy"_L1, @"doc.on.doc"},
+ {"edit-cut"_L1, @"scissors"},
+ {"edit-delete"_L1, @"delete.left"},
+ {"edit-find"_L1, @"magnifyingglass"},
+ //{"edit-find-replace"_L1, @"arrow.up.left.and.down.right.magnifyingglass"},
+ {"edit-paste"_L1, @"clipboard"},
+ {"edit-redo"_L1, @"arrowshape.turn.up.right"},
+ //{"edit-select-all"_L1, @""},
+ {"edit-undo"_L1, @"arrowshape.turn.up.left"},
+ {"folder-new"_L1, @"folder.badge.plus"},
+ {"format-indent-less"_L1, @"decrease.indent"},
+ {"format-indent-more"_L1, @"increase.indent"},
+ {"format-justify-center"_L1, @"text.aligncenter"},
+ {"format-justify-fill"_L1, @"text.justify"},
+ {"format-justify-left"_L1, @"text.justify.left"},
+ {"format-justify-right"_L1, @"text.justify.right"},
+ {"format-text-direction-ltr"_L1, @"text.justify.leading"},
+ {"format-text-direction-rtl"_L1, @"text.justify.trailing"},
+ {"format-text-bold"_L1, @"bold"},
+ {"format-text-italic"_L1, @"italic"},
+ {"format-text-underline"_L1, @"underline"},
+ {"format-text-strikethrough"_L1, @"strikethrough"},
+ //{"go-bottom"_L1, @""},
+ {"go-down"_L1, @"arrowshape.down"},
+ {"go-first"_L1, @"increase.indent"},
+ {"go-home"_L1, @"house"},
+ //{"go-jump"_L1, @""},
+ //{"go-last"_L1, @""},
+ {"go-next"_L1, @"arrowshape.right"},
+ {"go-previous"_L1, @"arrowshape.left"},
+ //{"go-top"_L1, @""},
+ {"go-up"_L1, @"arrowshape.up"},
+ {"help-about"_L1, @"info.circle"},
+ //{"help-contents"_L1, @""},
+ {"help-faq"_L1, @"questionmark.app"},
+ {"insert-image"_L1, @"photo.badge.plus"},
+ {"insert-link"_L1, @"link.badge.plus"},
+ //{"insert-object"_L1, @""},
+ {"insert-text"_L1, @"textformat"},
+ {"list-add"_L1, @"plus.circle"},
+ {"list-remove"_L1, @"minus.circle"},
+ {"mail-forward"_L1, @"arrowshape.turn.up.right"},
+ {"mail-mark-important"_L1, @"star"},
+ {"mail-mark-junk"_L1, @"xmark.bin"},
+ {"mail-mark-notjunk"_L1, @"trash.slash"},
+ {"mail-mark-read"_L1, @"envelope.open"},
+ {"mail-mark-unread"_L1, @"envelope.fill"},
+ {"mail-message-new"_L1, @"square.and.pencil"},
+ {"mail-reply-all"_L1, @"arrowshape.turn.up.left.2"},
+ {"mail-reply-sender"_L1, @"arrowshape.turn.up.left"},
+ {"mail-send"_L1, @"paperplane"},
+ {"mail-send-receive"_L1, @"envelope.arrow.triangle.branch"},
+ {"media-eject"_L1, @"eject"},
+ {"media-playback-pause"_L1, @"pause"},
+ {"media-playback-start"_L1, @"play"},
+ {"media-playback-stop"_L1, @"stop"},
+ {"media-record"_L1, @"record.circle"},
+ {"media-seek-backward"_L1, @"backward"},
+ {"media-seek-forward"_L1, @"forward"},
+ {"media-skip-backward"_L1, @"backward.end.alt"},
+ {"media-skip-forward"_L1, @"forward.end.alt"},
+ {"object-flip-horizontal"_L1, @"rectangle.landscape.rotate"},
+ {"object-flip-vertical"_L1, @"rectangle.portrait.rotate"},
+ {"object-rotate-left"_L1, @"rotate.left"},
+ {"object-rotate-right"_L1, @"rotate.right"},
+ {"process-stop"_L1, @"stop.circle"},
+ {"system-lock-screen"_L1, @"lock.display"},
+ {"system-log-out"_L1, @"door.left.hand.open"},
+ //{"system-run"_L1, @""},
+ {"system-search"_L1, @"magnifyingglass"},
+ //{"system-reboot"_L1, @""},
+ {"system-shutdown"_L1, @"power"},
+ //{"tools-check-spelling"_L1, @""},
+ {"view-fullscreen"_L1, @"arrow.up.left.and.arrow.down.right"},
+ {"view-refresh"_L1, @"arrow.clockwise"},
+ {"view-restore"_L1, @"arrow.down.right.and.arrow.up.left"},
+ //{"view-sort-ascending"_L1, @""},
+ //{"view-sort-descending"_L1, @""},
+ {"window-close"_L1, @"xmark.circle"},
+ {"window-new"_L1, @"macwindow.badge.plus"},
+ {"zoom-fit-best"_L1, @"square.arrowtriangle.4.outward"},
+ {"zoom-in"_L1, @"plus.magnifyingglass"},
+ //{"zoom-original"_L1, @""},
+ {"zoom-out"_L1, @"minus.magnifyingglass"},
+ {"process-working"_L1, @"circle.dotted"},
+ //{"accessories-calculator"_L1, @""},
+ //{"accessories-character-map"_L1, @""},
+ {"accessories-dictionary"_L1, @"character.book.closed"},
+ {"accessories-text-editor"_L1, @"textformat"},
+ {"help-browser"_L1, @"folder.badge.questionmark"},
+ {"multimedia-volume-control"_L1, @"speaker.wave.3"},
+ {"preferences-desktop-accessibility"_L1, @"accessibility"},
+ //{"preferences-desktop-font"_L1, @""},
+ {"preferences-desktop-keyboard"_L1, @"keyboard.badge.ellipsis"},
+ //{"preferences-desktop-locale"_L1, @""},
+ //{"preferences-desktop-multimedia"_L1, @""},
+ //{"preferences-desktop-screensaver"_L1, @""},
+ //{"preferences-desktop-theme"_L1, @""},
+ //{"preferences-desktop-wallpaper"_L1, @""},
+ {"system-file-manager"_L1, @"folder.badge.gearshape"},
+ //{"system-software-install"_L1, @""},
+ //{"system-software-update"_L1, @""}, d
+ //{"utilities-system-monitor"_L1, @""},
+ {"utilities-terminal"_L1, @"apple.terminal"},
+ //{"applications-accessories"_L1, @""},
+ //{"applications-development"_L1, @""},
+ //{"applications-engineering"_L1, @""},
+ {"applications-games"_L1, @"gamecontroller"},
+ //{"applications-graphics"_L1, @""},
+ {"applications-internet"_L1, @"network"},
+ {"applications-multimedia"_L1, @"tv.and.mediabox"},
+ //{"applications-office"_L1, @""},
+ //{"applications-other"_L1, @""},
+ {"applications-science"_L1, @"atom"},
+ //{"applications-system"_L1, @""},
+ //{"applications-utilities"_L1, @""},
+ {"preferences-desktop"_L1, @"menubar.dock.rectangle"},
+ //{"preferences-desktop-peripherals"_L1, @""},
+ //{"preferences-desktop-personal"_L1, @""},
+ //{"preferences-other"_L1, @""},
+ //{"preferences-system"_L1, @""},
+ {"preferences-system-network"_L1, @"network"},
+ {"system-help"_L1, @"questionmark.diamond"},
+ {"audio-card"_L1, @"waveform.circle"},
+ {"audio-input-microphone"_L1, @"mic"},
+ {"battery"_L1, @"battery.100percent"},
+ {"camera-photo"_L1, @"camera"},
+ {"camera-video"_L1, @"video"},
+ {"camera-web"_L1, @"web.camera"},
+ {"computer"_L1, @"desktopcomputer"},
+ {"drive-harddisk"_L1, @"internaldrive"},
+ {"drive-optical"_L1, @"opticaldiscdrive"},
+ {"drive-removable-media"_L1, @"externaldrive"},
+ {"input-gaming"_L1, @"gamecontroller"}, // "games" also using this one
+ {"input-keyboard"_L1, @"keyboard"},
+ {"input-mouse"_L1, @"computermouse"},
+ {"input-tablet"_L1, @"ipad"},
+ {"media-flash"_L1, @"mediastick"},
+ //{"media-floppy"_L1, @""},
+ //{"media-optical"_L1, @""},
+ {"media-tape"_L1, @"recordingtape"},
+ //{"modem"_L1, @""},
+ {"multimedia-player"_L1, @"play.rectangle"},
+ {"network-wired"_L1, @"app.connected.to.app.below.fill"},
+ {"network-wireless"_L1, @"wifi"},
+ //{"pda"_L1, @""},
+ {"phone"_L1, @"iphone"},
+ {"printer"_L1, @"printer"},
+ {"scanner"_L1, @"scanner"},
+ {"video-display"_L1, @"play.display"},
+ //{"emblem-default"_L1, @""},
+ {"emblem-documents"_L1, @"doc.circle"},
+ {"emblem-downloads"_L1, @"arrow.down.circle"},
+ {"emblem-favorite"_L1, @"star"},
+ {"emblem-important"_L1, @"exclamationmark.bubble.circle"},
+ {"emblem-mail"_L1, @"envelope"},
+ {"emblem-photos"_L1, @"photo.stack"},
+ //{"emblem-readonly"_L1, @""},
+ {"emblem-shared"_L1, @"folder.badge.person.crop"},
+ {"emblem-symbolic-link"_L1, @"link.circle"},
+ {"emblem-synchronized"_L1, @"arrow.triangle.2.circlepath.circle"},
+ {"emblem-system"_L1, @"gear"},
+ //{"emblem-unreadable"_L1, @""},
+ {"folder"_L1, @"folder"},
+ //{"folder-remote"_L1, @""},
+ {"network-server"_L1, @"server.rack"},
+ //{"network-workgroup"_L1, @""},
+ //{"start-here"_L1, @""},
+ {"user-bookmarks"_L1, @"bookmark.circle"},
+ {"user-desktop"_L1, @"desktopcomputer"}, //"computer" also using this one
+ {"user-home"_L1, @"house"}, //"go-home" also using this one
+ {"user-trash"_L1, @"trash"},
+ {"appointment-missed"_L1, @"calendar.badge.exclamationmark"},
+ {"appointment-soon"_L1, @"calendar.badge.clock"},
+ {"audio-volume-high"_L1, @"speaker.wave.3"},
+ {"audio-volume-low"_L1, @"speaker.wave.1"},
+ {"audio-volume-medium"_L1, @"speaker.wave.2"},
+ {"audio-volume-muted"_L1, @"speaker.slash"},
+ {"battery-caution"_L1, @"minus.plus.batteryblock.exclamationmark"},
+ {"battery-low"_L1, @"battery.25percent"}, // there are different levels that can be low battery
+ {"dialog-error"_L1, @"exclamationmark.bubble"},
+ {"dialog-information"_L1, @"info.circle"},
+ {"dialog-password"_L1, @"lock"},
+ {"dialog-question"_L1, @"questionmark.circle"},
+ {"dialog-warning"_L1, @"exclamationmark.octagon"},
+ {"folder-drag-accept"_L1, @"plus.rectangle.on.folder"},
+ //{"folder-open"_L1, @""},
+ {"folder-visiting"_L1, @"folder.circle"},
+ {"image-loading"_L1, @"photo.circle"},
+ {"image-missing"_L1, @"photo"},
+ {"mail-attachment"_L1, @"paperclip"},
+ {"mail-unread"_L1, @"envelope.badge"},
+ {"mail-read"_L1, @"envelope.open"},
+ {"mail-replied"_L1, @"arrowshape.turn.up.left"},
+ //{"mail-signed"_L1, @""},
+ //{"mail-signed-verified"_L1, @""},
+ {"media-playlist-repeat"_L1, @"repet"},
+ {"media-playlist-shuffle"_L1, @"shuffle"},
+ //{"network-error"_L1, @""},
+ //{"network-idle"_L1, @""},
+ {"network-offline"_L1, @"network.slash"},
+ //{"network-receive"_L1, @""},
+ //{"network-transmit"_L1, @""},
+ //{"network-transmit-receive"_L1, @""},
+ //{"printer-error"_L1, @""},
+ {"printer-printing"_L1, @"printer.dotmatrix.filled.and.paper"}, // not sure
+ {"security-high"_L1, @"lock.shield"},
+ //{"security-medium"_L1, @""},
+ {"security-low"_L1, @"lock.trianglebadge.exclamationmark"},
+ {"software-update-available"_L1, @"arrowshape.up.circle"},
+ {"software-update-urgent"_L1, @"exclamationmark.transmission"},
+ {"sync-error"_L1, @"exclamationmark.arrow.triangle.2.circlepath"},
+ {"sync-synchronizing"_L1, @"arrow.triangle.2.circlepath"},
+ {"task-due"_L1, @"clock.badge.exclamationmark"},
+ {"task-past-due"_L1, @"clock.badge.xmark"},
+ {"user-available"_L1, @"person.crop.circle.badge.checkmark"},
+ {"user-away"_L1, @"person.crop.circle.badge.clock"},
+ //{"user-idle"_L1, @""},
+ {"user-offline"_L1, @"person.crop.circle.badge.xmark"},
+ //{"user-trash-full"_L1, @""},
+ {"weather-clear"_L1, @"sun.max"},
+ {"weather-clear-night"_L1, @"moon"},
+ {"weather-few-clouds"_L1, @"cloud.sun"},
+ {"weather-few-clouds-night"_L1, @"cloud.moon"},
+ {"weather-fog"_L1, @"cloud.fog"},
+ {"weather-overcast"_L1, @"cloud"},
+ //{"weather-severe-alert"_L1, @""},
+ {"weather-showers"_L1, @"cloud.rain"},
+ //{"weather-showers-scattered"_L1, @""},
+ {"weather-snow"_L1, @"cloud.snow"},
+ {"weather-storm"_L1, @"tropicalstorm"},
+ };
+ const auto it = std::find_if(std::begin(iconMap), std::end(iconMap), [iconName](const auto &c){
+ return c.first == iconName;
+ });
+ NSString *systemIconName = it != std::end(iconMap) ? it->second : iconName.toNSString();
+#if defined(Q_OS_MACOS)
+ return [NSImage imageWithSystemSymbolName:systemIconName accessibilityDescription:nil];
+#elif defined(QT_PLATFORM_UIKIT)
+ return [UIImage systemImageNamed:systemIconName];
+#endif
+}
+}
+
+QAppleIconEngine::QAppleIconEngine(const QString &iconName)
+ : m_iconName(iconName), m_image(loadImage(iconName))
+{
+ if (m_image)
+ [m_image retain];
+}
+
+QAppleIconEngine::~QAppleIconEngine()
+{
+ if (m_image)
+ [m_image release];
+}
+
+QIconEngine *QAppleIconEngine::clone() const
+{
+ return new QAppleIconEngine(m_iconName);
+}
+
+QString QAppleIconEngine::key() const
+{
+ return u"QAppleIconEngine"_s;
+}
+
+QString QAppleIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QAppleIconEngine::isNull()
+{
+ return m_image == nullptr;
+}
+
+QList<QSize> QAppleIconEngine::availableIconSizes(double aspectRatio)
+{
+ const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
+ const QList<QSize> sizes = {
+ {qRound(16 * devicePixelRatio), qRound(16. * devicePixelRatio / aspectRatio)},
+ {qRound(32 * devicePixelRatio), qRound(32. * devicePixelRatio / aspectRatio)},
+ {qRound(64 * devicePixelRatio), qRound(64. * devicePixelRatio / aspectRatio)},
+ {qRound(128 * devicePixelRatio), qRound(128. * devicePixelRatio / aspectRatio)},
+ {qRound(256 * devicePixelRatio), qRound(256. * devicePixelRatio / aspectRatio)},
+ };
+ return sizes;
+}
+
+QList<QSize> QAppleIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ const double aspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
+ return availableIconSizes(aspectRatio);
+}
+
+QSize QAppleIconEngine::actualSize(const QSize &size, QIcon::Mode /*mode*/, QIcon::State /*state*/)
+{
+ const double inputAspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
+ const double outputAspectRatio = size.width() / size.height();
+ QSize result = size;
+ if (outputAspectRatio > inputAspectRatio)
+ result.rwidth() = result.height() * inputAspectRatio;
+ else
+ result.rheight() = result.width() / inputAspectRatio;
+ return result;
+}
+
+QPixmap QAppleIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+namespace {
+#if defined(Q_OS_MACOS)
+auto *configuredImage(const NSImage *image, const QColor &color)
+{
+ auto *config = [NSImageSymbolConfiguration configurationWithPointSize:48
+ weight:NSFontWeightRegular
+ scale:NSImageSymbolScaleLarge];
+ if (@available(macOS 12, *)) {
+ auto *primaryColor = [NSColor colorWithSRGBRed:color.redF()
+ green:color.greenF()
+ blue:color.blueF()
+ alpha:color.alphaF()];
+
+ auto *colorConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
+ config = [config configurationByApplyingConfiguration:colorConfig];
+ }
+
+ return [image imageWithSymbolConfiguration:config];
+}
+#elif defined(QT_PLATFORM_UIKIT)
+auto *configuredImage(const UIImage *image, const QColor &color)
+{
+ auto *config = [UIImageSymbolConfiguration configurationWithPointSize:48
+ weight:UIImageSymbolWeightRegular
+ scale:UIImageSymbolScaleLarge];
+
+ if (@available(iOS 15, *)) {
+ auto *primaryColor = [UIColor colorWithRed:color.redF()
+ green:color.greenF()
+ blue:color.blueF()
+ alpha:color.alphaF()];
+
+ auto *colorConfig = [UIImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
+ config = [config configurationByApplyingConfiguration:colorConfig];
+ }
+ return [image imageByApplyingSymbolConfiguration:config];
+}
+#endif
+}
+
+QPixmap QAppleIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ const QSize paintSize = actualSize(size, mode, state);
+ const QSize paintOffset = paintSize != size
+ ? (QSizeF(size - paintSize) * 0.5).toSize()
+ : QSize();
+
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.setDevicePixelRatio(scale);
+ m_pixmap.fill(Qt::transparent);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(paintOffset.width(), paintOffset.height(),
+ paintSize.width(), paintSize.height()), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+ return m_pixmap;
+}
+
+void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ QColor color;
+ const QPalette palette;
+ switch (mode) {
+ case QIcon::Normal:
+ color = palette.color(QPalette::Inactive, QPalette::Text);
+ break;
+ case QIcon::Disabled:
+ color = palette.color(QPalette::Disabled, QPalette::Text);
+ break;
+ case QIcon::Active:
+ color = palette.color(QPalette::Active, QPalette::Text);
+ break;
+ case QIcon::Selected:
+ color = palette.color(QPalette::Active, QPalette::HighlightedText);
+ break;
+ }
+ const auto *image = configuredImage(m_image, color);
+
+ QMacCGContext ctx(painter);
+
+#if defined(Q_OS_MACOS)
+ NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:gc];
+
+ const NSSize pixmapSize = NSMakeSize(rect.width(), rect.height());
+ [image setSize:pixmapSize];
+ const NSRect sourceRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height);
+ const NSRect iconRect = NSMakeRect(rect.x(), rect.y(), pixmapSize.width, pixmapSize.height);
+
+ [image drawInRect:iconRect fromRect:sourceRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil];
+ [NSGraphicsContext restoreGraphicsState];
+#elif defined(QT_PLATFORM_UIKIT)
+ UIGraphicsPushContext(ctx);
+ const CGRect cgrect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
+ [image drawInRect:cgrect];
+ UIGraphicsPopContext();
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/darwin/qappleiconengine_p.h b/src/gui/platform/darwin/qappleiconengine_p.h
new file mode 100644
index 0000000000..2a4ff7fc64
--- /dev/null
+++ b/src/gui/platform/darwin/qappleiconengine_p.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QAPPLEICONENGINE_P_H
+#define QAPPLEICONENGINE_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 <QtGui/qiconengine.h>
+
+#include <QtCore/private/qcore_mac_p.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(UIImage);
+Q_FORWARD_DECLARE_OBJC_CLASS(NSImage);
+
+QT_BEGIN_NAMESPACE
+
+class Q_GUI_EXPORT QAppleIconEngine : public QIconEngine
+{
+public:
+ QAppleIconEngine(const QString &iconName);
+ ~QAppleIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+ static QList<QSize> availableIconSizes(double aspectRatio = 1.0);
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+
+ const QString m_iconName;
+#if defined(Q_OS_MACOS)
+ const NSImage *m_image;
+#elif defined(QT_PLATFORM_UIKIT)
+ const UIImage *m_image;
+#endif
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QAPPLEICONENGINE_P_H
diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm
index f7dbc1990d..b8ff5c9d6d 100644
--- a/src/gui/platform/darwin/qapplekeymapper.mm
+++ b/src/gui/platform/darwin/qapplekeymapper.mm
@@ -18,7 +18,6 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper");
Q_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys");
static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers)
@@ -37,36 +36,6 @@ static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers m
return swappedModifiers;
}
-Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
- NSString *charactersIgnoringModifiers, QString &text)
-{
- if ([characters isEqualToString:@"\t"]) {
- if (qtModifiers & Qt::ShiftModifier)
- return Qt::Key_Backtab;
- return Qt::Key_Tab;
- } else if ([characters isEqualToString:@"\r"]) {
- if (qtModifiers & Qt::KeypadModifier)
- return Qt::Key_Enter;
- return Qt::Key_Return;
- }
- if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
- QChar ch;
- if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
- ([charactersIgnoringModifiers length] != 0)) {
- ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
- } else if ([characters length] != 0) {
- ch = QChar([characters characterAtIndex:0]);
- }
- if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
- (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
- text = QString::fromNSString(characters);
- }
- if (!ch.isNull())
- return Qt::Key(ch.toUpper().unicode());
- }
- return Qt::Key_unknown;
-}
-
#ifdef Q_OS_MACOS
static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = {
{ NSEventModifierFlagShift, Qt::ShiftModifier },
@@ -384,7 +353,7 @@ Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode)
// ------------------------------------------------
-Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers()
+Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const
{
return fromCocoaModifiers(NSEvent.modifierFlags);
}
@@ -538,11 +507,9 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt
where each modifier-key combination has been mapped to the
key it will produce.
*/
-QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
+QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
{
- QList<int> ret;
-
- qCDebug(lcQpaKeyMapper) << "Computing possible keys for" << event;
+ QList<QKeyCombination> ret;
const auto nativeVirtualKey = event->nativeVirtualKey();
if (!nativeVirtualKey)
@@ -555,16 +522,49 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
auto eventModifiers = event->modifiers();
- // The complete set of event modifiers, along with the
- // unmodified key, is always a valid key combination,
- // and the first priority.
- ret << int(eventModifiers) + int(unmodifiedKey);
+ int startingModifierLayer = 0;
+ if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
+ // When the Command key is pressed AppKit seems to do key equivalent
+ // matching using a Latin/Roman interpretation of the current keyboard
+ // layout. For example, for a Greek layout, pressing Option+Command+C
+ // produces a key event with chars="ç" and unmodchars="ψ", but AppKit
+ // still treats this as a match for a key equivalent of Option+Command+C.
+ // We can't do the same by just applying the modifiers to our key map,
+ // as that too contains "ψ" for the Option+Command combination. What we
+ // can do instead is take advantage of the fact that the Command
+ // modifier layer in all/most keyboard layouts contains a Latin
+ // layer. We then combine that with the modifiers of the event
+ // to produce the resulting "Latin" key combination.
+ static constexpr int kCommandLayer = 2;
+ ret << QKeyCombination::fromCombined(
+ int(eventModifiers) + int(keyMap[kCommandLayer]));
+
+ // If the unmodified key is outside of Latin1, we also treat
+ // that as a valid key combination, even if AppKit natively
+ // does not. For example, for a Greek layout, we still want
+ // to support Option+Command+ψ as a key combination, as it's
+ // unlikely to clash with the Latin key combination we added
+ // above.
+
+ // However, if the unmodified key is within Latin1, we skip
+ // it, to avoid these types of conflicts. For example, in
+ // the same Greek layout, pressing the key next to Tab will
+ // produce a Latin ';' symbol, but we've already treated that
+ // as 'q' above, thanks to the Command modifier, so we skip
+ // the potential Command+; key combination. This is also in
+ // line with what AppKit natively does.
+
+ // Skipping Latin1 unmodified keys also handles the case of
+ // a Latin layout, where the unmodified and modified keys
+ // are the same.
+
+ if (unmodifiedKey <= 0xff)
+ startingModifierLayer = 1;
+ }
// FIXME: We only compute the first 8 combinations. Why?
- for (int i = 1; i < 8; ++i) {
+ for (int i = startingModifierLayer; i < 15; ++i) {
auto keyAfterApplyingModifiers = keyMap[i];
- if (keyAfterApplyingModifiers == unmodifiedKey)
- continue;
if (!keyAfterApplyingModifiers)
continue;
@@ -575,18 +575,39 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
// If the event includes more modifiers than the candidate they
// will need to be included in the resulting key combination.
auto additionalModifiers = eventModifiers & ~candidateModifiers;
- ret << int(additionalModifiers) + int(keyAfterApplyingModifiers);
- }
- }
- if (lcQpaKeyMapper().isDebugEnabled()) {
- qCDebug(lcQpaKeyMapper) << "Possible keys:";
- for (int keyAndModifiers : ret) {
- auto keyCombination = QKeyCombination::fromCombined(keyAndModifiers);
- auto keySequence = QKeySequence(keyCombination);
- qCDebug(lcQpaKeyMapper).verbosity(0) << "\t-"
- << keyCombination << "/" << keySequence << "/"
- << qUtf8Printable(keySequence.toString(QKeySequence::NativeText));
+ auto keyCombination = QKeyCombination::fromCombined(
+ int(additionalModifiers) + int(keyAfterApplyingModifiers));
+
+ // If there's an existing key combination with the same key,
+ // but a different set of modifiers, we want to choose only
+ // one of them, by priority (see below).
+ const auto existingCombination = std::find_if(
+ ret.begin(), ret.end(), [&](auto existingCombination) {
+ return existingCombination.key() == keyAfterApplyingModifiers;
+ });
+
+ if (existingCombination != ret.end()) {
+ // We prioritize the combination with the more specific
+ // modifiers. In the case where the number of modifiers
+ // are the same, we want to prioritize Command over Option
+ // over Control over Shift. Unfortunately the order (and
+ // hence value) of the modifiers in Qt::KeyboardModifier
+ // does not match our preferred order when Control and
+ // Meta is switched, but we can work around that by
+ // explicitly swapping the modifiers and using that
+ // for the comparison. This also works when the
+ // Qt::AA_MacDontSwapCtrlAndMeta application attribute
+ // is set, as the incoming modifiers are then left
+ // as is, and we can still trust the order.
+ auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers());
+ auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers);
+ if (replacementModifiers > existingModifiers)
+ *existingCombination = keyCombination;
+ } else {
+ // All is good, no existing combination has this key
+ ret << keyCombination;
+ }
}
}
@@ -597,6 +618,36 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
#else // iOS
+Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
+ NSString *charactersIgnoringModifiers, QString &text)
+{
+ if ([characters isEqualToString:@"\t"]) {
+ if (qtModifiers & Qt::ShiftModifier)
+ return Qt::Key_Backtab;
+ return Qt::Key_Tab;
+ } else if ([characters isEqualToString:@"\r"]) {
+ if (qtModifiers & Qt::KeypadModifier)
+ return Qt::Key_Enter;
+ return Qt::Key_Return;
+ }
+ if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
+ QChar ch;
+ if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
+ ([charactersIgnoringModifiers length] != 0)) {
+ ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
+ } else if ([characters length] != 0) {
+ ch = QChar([characters characterAtIndex:0]);
+ }
+ if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
+ (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
+ text = QString::fromNSString(characters);
+ }
+ if (!ch.isNull())
+ return Qt::Key(ch.toUpper().unicode());
+ }
+ return Qt::Key_unknown;
+}
+
// Keyboard keys (non-modifiers)
API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode)
{
diff --git a/src/gui/platform/darwin/qapplekeymapper_p.h b/src/gui/platform/darwin/qapplekeymapper_p.h
index 34557c8ede..1f3494d16f 100644
--- a/src/gui/platform/darwin/qapplekeymapper_p.h
+++ b/src/gui/platform/darwin/qapplekeymapper_p.h
@@ -19,6 +19,8 @@
#include <Carbon/Carbon.h>
#endif
+#include <qpa/qplatformkeymapper.h>
+
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtGui/QKeyEvent>
@@ -27,13 +29,12 @@
QT_BEGIN_NAMESPACE
-class Q_GUI_EXPORT QAppleKeyMapper
+class Q_GUI_EXPORT QAppleKeyMapper : public QPlatformKeyMapper
{
public:
- static Qt::KeyboardModifiers queryKeyboardModifiers();
- QList<int> possibleKeys(const QKeyEvent *event) const;
- static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters,
- NSString *charactersIgnoringModifiers, QString &text);
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const override;
+
#ifdef Q_OS_MACOS
static Qt::KeyboardModifiers fromCocoaModifiers(NSEventModifierFlags cocoaModifiers);
static NSEventModifierFlags toCocoaModifiers(Qt::KeyboardModifiers);
@@ -41,6 +42,9 @@ public:
static QChar toCocoaKey(Qt::Key key);
static Qt::Key fromCocoaKey(QChar keyCode);
#else
+ static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters,
+ NSString *charactersIgnoringModifiers, QString &text);
+
static Qt::Key fromUIKitKey(NSString *keyCode);
static Qt::KeyboardModifiers fromUIKitModifiers(ulong uikitModifiers);
static ulong toUIKitModifiers(Qt::KeyboardModifiers);
diff --git a/src/gui/platform/darwin/qmacmimeregistry.mm b/src/gui/platform/darwin/qmacmimeregistry.mm
index acbe671e1a..6710a0656f 100644
--- a/src/gui/platform/darwin/qmacmimeregistry.mm
+++ b/src/gui/platform/darwin/qmacmimeregistry.mm
@@ -21,20 +21,6 @@ Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList)
// implemented in qutimimeconverter.mm
void registerBuiltInTypes();
-/*!
- \fn void qRegisterDraggedTypes(const QStringList &types)
- \relates QUtiMimeConverter
-
- Registers the given \a types as custom pasteboard types.
-
- This function should be called to enable the Drag and Drop events
- for custom pasteboard types on Cocoa implementations. This is required
- in addition to a QUtiMimeConverter subclass implementation. By default
- drag and drop is enabled for all standard pasteboard types.
-
- \sa QUtiMimeConverter
-*/
-
void registerDraggedTypes(const QStringList &types)
{
(*globalDraggedTypesList()) += types;
diff --git a/src/gui/platform/darwin/qutimimeconverter.mm b/src/gui/platform/darwin/qutimimeconverter.mm
index 975f00cf88..ee643fd0c6 100644
--- a/src/gui/platform/darwin/qutimimeconverter.mm
+++ b/src/gui/platform/darwin/qutimimeconverter.mm
@@ -3,6 +3,7 @@
#include <ImageIO/ImageIO.h>
#include <CoreFoundation/CoreFoundation.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include <QtCore/qsystemdetection.h>
#include <QtCore/qurl.h>
@@ -53,7 +54,21 @@ using namespace Qt::StringLiterals;
By subclasses this class, one can extend Qt's drag and drop
and clipboard handling to convert to and from unsupported, or proprietary, UTI formats.
- A subclass of QUtiMimeConverter will automatically be registered, and active, upon instantiation.
+ Construct an instance of your converter implementation after instantiating
+ QGuiApplication:
+
+ \code
+ int main(int argc, char **argv)
+ {
+ QGuiApplication app(argc, argv);
+ JsonMimeConverter jsonConverter;
+ }
+ \endcode
+
+ Destroying the instance will unregister the converter and remove support
+ for the conversion. It is also valid to heap-allocate the converter
+ instance; Qt takes ownership and will delete the converter object during
+ QGuiApplication shut-down.
Qt has predefined support for the following UTIs:
\list
@@ -94,6 +109,8 @@ QUtiMimeConverter::QUtiMimeConverter(HandlerScope scope)
/*!
Constructs a new conversion object and adds it to the
globally accessed list of available converters.
+
+ Call this constructor after QGuiApplication has been created.
*/
QUtiMimeConverter::QUtiMimeConverter()
: QUtiMimeConverter(HandlerScopeFlag::All)
@@ -763,7 +780,7 @@ QList<QByteArray> QMacMimeTiff::convertFromMime(const QString &mime,
QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0);
QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data,
- kUTTypeTIFF, 1, 0);
+ (CFStringRef)UTTypeTIFF.identifier, 1, 0);
if (!imageDestination)
return QList<QByteArray>();
diff --git a/src/gui/platform/ios/PrivacyInfo.xcprivacy b/src/gui/platform/ios/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000000..bde2b167c7
--- /dev/null
+++ b/src/gui/platform/ios/PrivacyInfo.xcprivacy
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>NSPrivacyTracking</key>
+ <false/>
+ <key>NSPrivacyCollectedDataTypes</key>
+ <array/>
+ <key>NSPrivacyTrackingDomains</key>
+ <array/>
+ <key>NSPrivacyAccessedAPITypes</key>
+ <array>
+ <dict>
+ <key>NSPrivacyAccessedAPIType</key>
+ <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
+ <key>NSPrivacyAccessedAPITypeReasons</key>
+ <array>
+ <string>35F9.1</string> <!-- QUIView event handling -->
+ </array>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/src/gui/platform/ios/qiosnativeinterface.cpp b/src/gui/platform/ios/qiosnativeinterface.cpp
new file mode 100644
index 0000000000..c942709e33
--- /dev/null
+++ b/src/gui/platform/ios/qiosnativeinterface.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtGui/private/qguiapplication_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QNativeInterface::Private;
+
+#if defined(Q_OS_VISIONOS)
+
+/*!
+ \class QNativeInterface::QVisionOSApplication
+ \since 6.8
+ \internal
+ \preliminary
+ \brief Native interface to QGuiApplication, to be retrieved from QPlatformIntegration.
+ \inmodule QtGui
+ \ingroup native-interfaces
+*/
+
+QT_DEFINE_NATIVE_INTERFACE(QVisionOSApplication);
+
+#endif // Q_OS_VISIONOS
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/macos/qcocoanativeinterface.mm b/src/gui/platform/macos/qcocoanativeinterface.mm
index 58c19c7781..cb6acb4496 100644
--- a/src/gui/platform/macos/qcocoanativeinterface.mm
+++ b/src/gui/platform/macos/qcocoanativeinterface.mm
@@ -1,6 +1,7 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include <QtGui/qtgui-config.h>
#ifndef QT_NO_OPENGL
# include <QtGui/private/qopenglcontext_p.h>
#endif
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
index d2469a0d26..b7fd035883 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
@@ -217,19 +217,19 @@ QDBusMenuShortcut QDBusMenuItem::convertKeySequence(const QKeySequence &sequence
QDBusMenuShortcut shortcut;
for (int i = 0; i < sequence.count(); ++i) {
QStringList tokens;
- int key = sequence[i].toCombined();
- if (key & Qt::MetaModifier)
+ auto modifiers = sequence[i].keyboardModifiers();
+ if (modifiers & Qt::MetaModifier)
tokens << QStringLiteral("Super");
- if (key & Qt::ControlModifier)
+ if (modifiers & Qt::ControlModifier)
tokens << QStringLiteral("Control");
- if (key & Qt::AltModifier)
+ if (modifiers & Qt::AltModifier)
tokens << QStringLiteral("Alt");
- if (key & Qt::ShiftModifier)
+ if (modifiers & Qt::ShiftModifier)
tokens << QStringLiteral("Shift");
- if (key & Qt::KeypadModifier)
+ if (modifiers & Qt::KeypadModifier)
tokens << QStringLiteral("Num");
- QString keyName = QKeySequencePrivate::keyName(key, QKeySequence::PortableText);
+ QString keyName = QKeySequencePrivate::keyName(sequence[i].key(), QKeySequence::PortableText);
if (keyName == "+"_L1)
tokens << QStringLiteral("plus");
else if (keyName == "-"_L1)
diff --git a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
index 5be223b510..0dff9b598e 100644
--- a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
+++ b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp
@@ -198,7 +198,10 @@ QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon)
if (!necessary)
return nullptr;
QTemporaryFile *ret = new QTemporaryFile(tempFileTemplate(), this);
- ret->open();
+ if (!ret->open()) {
+ delete ret;
+ return nullptr;
+ }
icon.pixmap(QSize(22, 22)).save(ret);
ret->close();
return ret;
diff --git a/src/gui/platform/unix/qgenericunixservices.cpp b/src/gui/platform/unix/qgenericunixservices.cpp
index 34d9c37e24..bfd2556b1e 100644
--- a/src/gui/platform/unix/qgenericunixservices.cpp
+++ b/src/gui/platform/unix/qgenericunixservices.cpp
@@ -354,9 +354,13 @@ private Q_SLOTS:
{
if (result != 0)
return;
- XDGDesktopColor color{};
- map.value(u"color"_s).value<QDBusArgument>() >> color;
- Q_EMIT colorPicked(color.toQColor());
+ if (map.contains(u"color"_s)) {
+ XDGDesktopColor color{};
+ map.value(u"color"_s).value<QDBusArgument>() >> color;
+ Q_EMIT colorPicked(color.toQColor());
+ } else {
+ Q_EMIT colorPicked({});
+ }
deleteLater();
}
@@ -418,9 +422,11 @@ QByteArray QGenericUnixServices::desktopEnvironment() const
template<typename F>
void runWithXdgActivationToken(F &&functionToCall)
{
+#if QT_CONFIG(wayland)
QWindow *window = qGuiApp->focusWindow();
if (!window) {
+ functionToCall({});
return;
}
@@ -430,13 +436,17 @@ void runWithXdgActivationToken(F &&functionToCall)
dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle());
if (!waylandWindow || !waylandApp) {
+ functionToCall({});
return;
}
- waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial());
QObject::connect(waylandWindow,
&QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated,
waylandWindow, functionToCall, Qt::SingleShotConnection);
+ waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial());
+#else
+ functionToCall({});
+#endif
}
bool QGenericUnixServices::openUrl(const QUrl &url)
@@ -554,9 +564,7 @@ QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
QString QGenericUnixServices::portalWindowIdentifier(QWindow *window)
{
- if (QGuiApplication::platformName() == QLatin1String("xcb"))
- return "x11:"_L1 + QString::number(window->winId(), 16);
-
+ Q_UNUSED(window);
return QString();
}
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp
index af2307fd09..67de34923f 100644
--- a/src/gui/platform/unix/qgenericunixthemes.cpp
+++ b/src/gui/platform/unix/qgenericunixthemes.cpp
@@ -148,7 +148,7 @@ public:
enum class Setting {
Theme,
ApplicationStyle,
- ColorTheme,
+ ColorScheme,
};
Q_ENUM(Setting)
@@ -276,7 +276,6 @@ void QGenericUnixThemeDBusListener::loadJson(const QString &fileName)
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
CHECK((error.error == QJsonParseError::NoError), error.errorString());
- qDebug() << doc;
CHECK(doc.isObject(), "Parse Error: Expected root object" << s_root);
const QJsonObject &root = doc.object();
@@ -376,7 +375,7 @@ void QGenericUnixThemeDBusListener::populateSignalMap()
ChangeSignal(Provider::Gtk, Setting::Theme));
m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1),
- ChangeSignal(Provider::Gnome, Setting::ColorTheme));
+ ChangeSignal(Provider::Gnome, Setting::ColorScheme));
const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE");
if (!saveJsonFile.isEmpty())
@@ -542,6 +541,48 @@ class QKdeThemePrivate : public QPlatformThemePrivate
{
public:
+ enum class KdeSettingType {
+ Root,
+ KDE,
+ Icons,
+ ToolBarIcons,
+ ToolBarStyle,
+ Fonts,
+ Colors,
+ };
+
+ enum class KdeSetting {
+ WidgetStyle,
+ ColorScheme,
+ SingleClick,
+ ShowIconsOnPushButtons,
+ IconTheme,
+ ToolBarIconSize,
+ ToolButtonStyle,
+ WheelScrollLines,
+ DoubleClickInterval,
+ StartDragDistance,
+ StartDragTime,
+ CursorBlinkRate,
+ Font,
+ Fixed,
+ MenuFont,
+ ToolBarFont,
+ ButtonBackground,
+ WindowBackground,
+ ViewForeground,
+ WindowForeground,
+ ViewBackground,
+ SelectionBackground,
+ SelectionForeground,
+ ViewBackgroundAlternate,
+ ButtonForeground,
+ ViewForegroundLink,
+ ViewForegroundVisited,
+ TooltipBackground,
+ TooltipForeground,
+ };
+
QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
@@ -552,7 +593,9 @@ public:
}
void refresh();
- static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings);
+ static QVariant readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &settings);
+ QVariant readKdeSetting(KdeSetting s) const;
+ void clearKdeSettings() const;
static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal);
static QFont *kdeFont(const QVariant &fontValue);
static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs);
@@ -576,8 +619,9 @@ public:
Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
void updateColorScheme(const QString &themeName);
-#ifndef QT_NO_DBUS
private:
+ mutable QHash<QString, QSettings *> kdeSettings;
+#ifndef QT_NO_DBUS
std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
bool initDbus();
void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
@@ -595,7 +639,7 @@ void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Prov
return;
switch (setting) {
- case QGenericUnixThemeDBusListener::Setting::ColorTheme:
+ case QGenericUnixThemeDBusListener::Setting::ColorScheme:
qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value;
break;
case QGenericUnixThemeDBusListener::Setting::Theme:
@@ -621,7 +665,7 @@ bool QKdeThemePrivate::initDbus()
settingChangedHandler(provider, setting, value);
};
- return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, wrapper);
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
}
#endif // QT_NO_DBUS
@@ -633,9 +677,136 @@ QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
#endif // QT_NO_DBUS
}
+static constexpr QLatin1StringView settingsPrefix(QKdeThemePrivate::KdeSettingType type)
+{
+ switch (type) {
+ case QKdeThemePrivate::KdeSettingType::Root:
+ return QLatin1StringView();
+ case QKdeThemePrivate::KdeSettingType::KDE:
+ return QLatin1StringView("KDE/");
+ case QKdeThemePrivate::KdeSettingType::Fonts:
+ return QLatin1StringView();
+ case QKdeThemePrivate::KdeSettingType::Colors:
+ return QLatin1StringView("Colors:");
+ case QKdeThemePrivate::KdeSettingType::Icons:
+ return QLatin1StringView("Icons/");
+ case QKdeThemePrivate::KdeSettingType::ToolBarIcons:
+ return QLatin1StringView("ToolbarIcons/");
+ case QKdeThemePrivate::KdeSettingType::ToolBarStyle:
+ return QLatin1StringView("Toolbar style/");
+ }
+ Q_UNREACHABLE_RETURN(QLatin1StringView());
+}
+
+static constexpr QKdeThemePrivate::KdeSettingType settingsType(QKdeThemePrivate::KdeSetting setting)
+{
+#define CASE(s, type) case QKdeThemePrivate::KdeSetting::s:\
+ return QKdeThemePrivate::KdeSettingType::type
+
+ switch (setting) {
+ CASE(WidgetStyle, Root);
+ CASE(ColorScheme, Root);
+ CASE(SingleClick, KDE);
+ CASE(ShowIconsOnPushButtons, KDE);
+ CASE(IconTheme, Icons);
+ CASE(ToolBarIconSize, ToolBarIcons);
+ CASE(ToolButtonStyle, ToolBarStyle);
+ CASE(WheelScrollLines, KDE);
+ CASE(DoubleClickInterval, KDE);
+ CASE(StartDragDistance, KDE);
+ CASE(StartDragTime, KDE);
+ CASE(CursorBlinkRate, KDE);
+ CASE(Font, Root);
+ CASE(Fixed, Root);
+ CASE(MenuFont, Root);
+ CASE(ToolBarFont, Root);
+ CASE(ButtonBackground, Colors);
+ CASE(WindowBackground, Colors);
+ CASE(ViewForeground, Colors);
+ CASE(WindowForeground, Colors);
+ CASE(ViewBackground, Colors);
+ CASE(SelectionBackground, Colors);
+ CASE(SelectionForeground, Colors);
+ CASE(ViewBackgroundAlternate, Colors);
+ CASE(ButtonForeground, Colors);
+ CASE(ViewForegroundLink, Colors);
+ CASE(ViewForegroundVisited, Colors);
+ CASE(TooltipBackground, Colors);
+ CASE(TooltipForeground, Colors);
+ };
+ Q_UNREACHABLE_RETURN(QKdeThemePrivate::KdeSettingType::Root);
+}
+#undef CASE
+
+static constexpr QLatin1StringView settingsKey(QKdeThemePrivate::KdeSetting setting)
+{
+ switch (setting) {
+ case QKdeThemePrivate::KdeSetting::WidgetStyle:
+ return QLatin1StringView("widgetStyle");
+ case QKdeThemePrivate::KdeSetting::ColorScheme:
+ return QLatin1StringView("ColorScheme");
+ case QKdeThemePrivate::KdeSetting::SingleClick:
+ return QLatin1StringView("SingleClick");
+ case QKdeThemePrivate::KdeSetting::ShowIconsOnPushButtons:
+ return QLatin1StringView("ShowIconsOnPushButtons");
+ case QKdeThemePrivate::KdeSetting::IconTheme:
+ return QLatin1StringView("Theme");
+ case QKdeThemePrivate::KdeSetting::ToolBarIconSize:
+ return QLatin1StringView("Size");
+ case QKdeThemePrivate::KdeSetting::ToolButtonStyle:
+ return QLatin1StringView("ToolButtonStyle");
+ case QKdeThemePrivate::KdeSetting::WheelScrollLines:
+ return QLatin1StringView("WheelScrollLines");
+ case QKdeThemePrivate::KdeSetting::DoubleClickInterval:
+ return QLatin1StringView("DoubleClickInterval");
+ case QKdeThemePrivate::KdeSetting::StartDragDistance:
+ return QLatin1StringView("StartDragDist");
+ case QKdeThemePrivate::KdeSetting::StartDragTime:
+ return QLatin1StringView("StartDragTime");
+ case QKdeThemePrivate::KdeSetting::CursorBlinkRate:
+ return QLatin1StringView("CursorBlinkRate");
+ case QKdeThemePrivate::KdeSetting::Font:
+ return QLatin1StringView("font");
+ case QKdeThemePrivate::KdeSetting::Fixed:
+ return QLatin1StringView("fixed");
+ case QKdeThemePrivate::KdeSetting::MenuFont:
+ return QLatin1StringView("menuFont");
+ case QKdeThemePrivate::KdeSetting::ToolBarFont:
+ return QLatin1StringView("toolBarFont");
+ case QKdeThemePrivate::KdeSetting::ButtonBackground:
+ return QLatin1StringView("Button/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::WindowBackground:
+ return QLatin1StringView("Window/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewForeground:
+ return QLatin1StringView("View/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::WindowForeground:
+ return QLatin1StringView("Window/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewBackground:
+ return QLatin1StringView("View/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::SelectionBackground:
+ return QLatin1StringView("Selection/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::SelectionForeground:
+ return QLatin1StringView("Selection/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewBackgroundAlternate:
+ return QLatin1StringView("View/BackgroundAlternate");
+ case QKdeThemePrivate::KdeSetting::ButtonForeground:
+ return QLatin1StringView("Button/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewForegroundLink:
+ return QLatin1StringView("View/ForegroundLink");
+ case QKdeThemePrivate::KdeSetting::ViewForegroundVisited:
+ return QLatin1StringView("View/ForegroundVisited");
+ case QKdeThemePrivate::KdeSetting::TooltipBackground:
+ return QLatin1StringView("Tooltip/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::TooltipForeground:
+ return QLatin1StringView("Tooltip/ForegroundNormal");
+ };
+ Q_UNREACHABLE_RETURN(QLatin1StringView());
+}
+
void QKdeThemePrivate::refresh()
{
resources.clear();
+ clearKdeSettings();
toolButtonStyle = Qt::ToolButtonTextBesideIcon;
toolBarIconSize = 0;
@@ -648,45 +819,39 @@ void QKdeThemePrivate::refresh()
else
iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen");
- QHash<QString, QSettings*> kdeSettings;
-
QPalette systemPalette = QPalette();
readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, &systemPalette);
resources.palettes[QPlatformTheme::SystemPalette] = new QPalette(systemPalette);
//## TODO tooltip color
- const QVariant styleValue = readKdeSetting(QStringLiteral("widgetStyle"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant styleValue = readKdeSetting(KdeSetting::WidgetStyle);
if (styleValue.isValid()) {
const QString style = styleValue.toString();
if (style != styleNames.front())
styleNames.push_front(style);
}
- const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs,
- kdeVersion, kdeSettings);
+ const QVariant colorScheme = readKdeSetting(KdeSetting::ColorScheme);
- if (colorScheme.isValid())
- updateColorScheme(colorScheme.toString());
- else
- m_colorScheme = Qt::ColorScheme::Unknown;
+ updateColorScheme(colorScheme.toString());
- const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant singleClickValue = readKdeSetting(KdeSetting::SingleClick);
if (singleClickValue.isValid())
singleClick = singleClickValue.toBool();
- const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant showIconsOnPushButtonsValue = readKdeSetting(KdeSetting::ShowIconsOnPushButtons);
if (showIconsOnPushButtonsValue.isValid())
showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool();
- const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant themeValue = readKdeSetting(KdeSetting::IconTheme);
if (themeValue.isValid())
iconThemeName = themeValue.toString();
- const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant toolBarIconSizeValue = readKdeSetting(KdeSetting::ToolBarIconSize);
if (toolBarIconSizeValue.isValid())
toolBarIconSize = toolBarIconSizeValue.toInt();
- const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant toolbarStyleValue = readKdeSetting(KdeSetting::ToolButtonStyle);
if (toolbarStyleValue.isValid()) {
const QString toolBarStyle = toolbarStyleValue.toString();
if (toolBarStyle == "TextBesideIcon"_L1)
@@ -697,35 +862,35 @@ void QKdeThemePrivate::refresh()
toolButtonStyle = Qt::ToolButtonTextUnderIcon;
}
- const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant wheelScrollLinesValue = readKdeSetting(KdeSetting::WheelScrollLines);
if (wheelScrollLinesValue.isValid())
wheelScrollLines = wheelScrollLinesValue.toInt();
- const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant doubleClickIntervalValue = readKdeSetting(KdeSetting::DoubleClickInterval);
if (doubleClickIntervalValue.isValid())
doubleClickInterval = doubleClickIntervalValue.toInt();
- const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant startDragDistValue = readKdeSetting(KdeSetting::StartDragDistance);
if (startDragDistValue.isValid())
startDragDist = startDragDistValue.toInt();
- const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant startDragTimeValue = readKdeSetting(KdeSetting::StartDragTime);
if (startDragTimeValue.isValid())
startDragTime = startDragTimeValue.toInt();
- const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant cursorBlinkRateValue = readKdeSetting(KdeSetting::CursorBlinkRate);
if (cursorBlinkRateValue.isValid()) {
cursorBlinkRate = cursorBlinkRateValue.toInt();
cursorBlinkRate = cursorBlinkRate > 0 ? qBound(200, cursorBlinkRate, 2000) : 0;
}
// Read system font, ignore 'smallestReadableFont'
- if (QFont *systemFont = kdeFont(readKdeSetting(QStringLiteral("font"), kdeDirs, kdeVersion, kdeSettings)))
+ if (QFont *systemFont = kdeFont(readKdeSetting(KdeSetting::Font)))
resources.fonts[QPlatformTheme::SystemFont] = systemFont;
else
resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize);
- if (QFont *fixedFont = kdeFont(readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (QFont *fixedFont = kdeFont(readKdeSetting(KdeSetting::Fixed))) {
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
} else {
fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize);
@@ -733,12 +898,12 @@ void QKdeThemePrivate::refresh()
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
}
- if (QFont *menuFont = kdeFont(readKdeSetting(QStringLiteral("menuFont"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (QFont *menuFont = kdeFont(readKdeSetting(KdeSetting::MenuFont))) {
resources.fonts[QPlatformTheme::MenuFont] = menuFont;
resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont);
}
- if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings)))
+ if (QFont *toolBarFont = kdeFont(readKdeSetting(KdeSetting::ToolBarFont)))
resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
QWindowSystemInterface::handleThemeChange();
@@ -748,7 +913,7 @@ void QKdeThemePrivate::refresh()
qDeleteAll(kdeSettings);
}
-QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
+QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
{
for (const QString &kdeDir : kdeDirs) {
QSettings *settings = kdeSettings.value(kdeDir);
@@ -760,6 +925,7 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList
}
}
if (settings) {
+ const QString key = settingsPrefix(settingsType(s)) + settingsKey(s);
const QVariant value = settings->value(key);
if (value.isValid())
return value;
@@ -768,6 +934,16 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList
return QVariant();
}
+QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s) const
+{
+ return readKdeSetting(s, kdeDirs, kdeVersion, kdeSettings);
+}
+
+void QKdeThemePrivate::clearKdeSettings() const
+{
+ kdeSettings.clear();
+}
+
// Reads the color from the KDE configuration, and store it in the
// palette with the given color role if found.
static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value)
@@ -783,7 +959,7 @@ static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVari
void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal)
{
- if (!kdeColor(pal, QPalette::Button, readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (!kdeColor(pal, QPalette::Button, readKdeSetting(KdeSetting::ButtonBackground, kdeDirs, kdeVersion, kdeSettings))) {
// kcolorscheme.cpp: SetDefaultColors
const QColor defaultWindowBackground(214, 210, 208);
const QColor defaultButtonBackground(223, 220, 217);
@@ -791,18 +967,18 @@ void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeV
return;
}
- kdeColor(pal, QPalette::Window, readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Text, readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::WindowText, readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Base, readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Highlight, readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::HighlightedText, readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::AlternateBase, readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ButtonText, readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Link, readKdeSetting(QStringLiteral("Colors:View/ForegroundLink"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::LinkVisited, readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ToolTipText, readKdeSetting(QStringLiteral("Colors:Tooltip/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Window, readKdeSetting(KdeSetting::WindowBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Text, readKdeSetting(KdeSetting::ViewForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::WindowText, readKdeSetting(KdeSetting::WindowForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Base, readKdeSetting(KdeSetting::ViewBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Highlight, readKdeSetting(KdeSetting::SelectionBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::HighlightedText, readKdeSetting(KdeSetting::SelectionForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::AlternateBase, readKdeSetting(KdeSetting::ViewBackgroundAlternate, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ButtonText, readKdeSetting(KdeSetting::ButtonForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Link, readKdeSetting(KdeSetting::ViewForegroundLink, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::LinkVisited, readKdeSetting(KdeSetting::ViewForegroundVisited, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(KdeSetting::TooltipBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ToolTipText, readKdeSetting(KdeSetting::TooltipForeground, kdeDirs, kdeVersion, kdeSettings));
// The above code sets _all_ color roles to "normal" colors. In KDE, the disabled
// color roles are calculated by applying various effects described in kdeglobals.
@@ -958,13 +1134,13 @@ Qt::ColorScheme QKdeTheme::colorScheme() const
/*!
\internal
- \brief QKdeTheme::setColorScheme - guess and set appearance for unix themes.
- KDE themes do not have an appearance property.
- The key words "dark" or "light" should be part of the theme name.
+ \brief QKdeTheme::updateColorScheme - guess and set a color scheme for unix themes.
+ KDE themes do not have a color scheme property.
+ The key words "dark" or "light" are usually part of the theme name.
This is, however, not a mandatory convention.
- If \param themeName contains a key word, the respective appearance is set.
- If it doesn't, the appearance is heuristically determined by comparing text and base color
+ If \param themeName contains a valid key word, the respective color scheme is set.
+ If it doesn't, the color scheme is heuristically determined by comparing text and base color
of the system palette.
*/
void QKdeThemePrivate::updateColorScheme(const QString &themeName)
@@ -992,7 +1168,6 @@ void QKdeThemePrivate::updateColorScheme(const QString &themeName)
m_colorScheme = Qt::ColorScheme::Unknown;
}
-
const QPalette *QKdeTheme::palette(Palette type) const
{
Q_D(const QKdeTheme);
@@ -1154,7 +1329,7 @@ bool QGnomeThemePrivate::initDbus()
updateColorScheme(value);
};
- return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, wrapper);
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
}
void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
diff --git a/src/gui/platform/unix/qunixnativeinterface.cpp b/src/gui/platform/unix/qunixnativeinterface.cpp
index 1f891de0f5..09561d9ada 100644
--- a/src/gui/platform/unix/qunixnativeinterface.cpp
+++ b/src/gui/platform/unix/qunixnativeinterface.cpp
@@ -231,10 +231,11 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEvdevKeyMapper);
#endif // QT_CONFIG(evdev)
-#if defined(Q_OS_UNIX)
+#if QT_CONFIG(wayland)
/*!
\class QNativeInterface::QWaylandApplication
+ \inheaderfile QGuiApplication
\since 6.5
\brief Native interface to a Wayland application.
@@ -271,19 +272,28 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEvdevKeyMapper);
\fn wl_seat *QNativeInterface::QWaylandApplication::lastInputSeat() const
\return the seat on which the last input event happened.
*/
+/*!
+ \fn wl_seat *QNativeInterface::QWaylandApplication::seat() const
+ \return the seat associated with the default input device.
+*/
QT_DEFINE_NATIVE_INTERFACE(QWaylandApplication);
/*!
- \class QNativeInterface::Private::QWaylandScreen
- \since 6.5
- \internal
- \brief Native interface to QPlatformScreen.
+ \class QNativeInterface::QWaylandScreen
+ \since 6.7
+ \brief Native interface to a screen on Wayland.
+
+ Accessed through QScreen::nativeInterface().
\inmodule QtGui
\ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
*/
-
-QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWaylandScreen);
+/*!
+ \fn wl_output *QNativeInterface::QWaylandScreen::output() const
+ \return the underlying wl_output of this QScreen.
+*/
+QT_DEFINE_NATIVE_INTERFACE(QWaylandScreen);
/*!
\class QNativeInterface::QWaylandWindow
@@ -296,6 +306,6 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWaylandScreen);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWaylandWindow);
-#endif // Q_OS_UNIX
+#endif // QT_CONFIG(wayland)
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qxkbcommon.cpp b/src/gui/platform/unix/qxkbcommon.cpp
index d254aeecdc..ed29db3005 100644
--- a/src/gui/platform/unix/qxkbcommon.cpp
+++ b/src/gui/platform/unix/qxkbcommon.cpp
@@ -17,8 +17,6 @@
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcXkbcommon, "qt.xkbcommon")
-
static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers,
xkb_state *state, xkb_keycode_t code,
bool superAsMeta, bool hyperAsMeta);
@@ -239,10 +237,14 @@ static constexpr const auto KeyTbl = qMakeArray(
Xkb2Qt<XKB_KEY_dead_small_schwa, Qt::Key_Dead_Small_Schwa>,
Xkb2Qt<XKB_KEY_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa>,
Xkb2Qt<XKB_KEY_dead_greek, Qt::Key_Dead_Greek>,
+/* The following four XKB_KEY_dead keys got removed in libxkbcommon 1.6.0
+ The define check is kind of version check here. */
+#ifdef XKB_KEY_dead_lowline
Xkb2Qt<XKB_KEY_dead_lowline, Qt::Key_Dead_Lowline>,
Xkb2Qt<XKB_KEY_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline>,
Xkb2Qt<XKB_KEY_dead_belowverticalline, Qt::Key_Dead_Belowverticalline>,
Xkb2Qt<XKB_KEY_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay>,
+#endif
// Special keys from X.org - This include multimedia keys,
// wireless/bluetooth/uwb keys, special launcher keys, etc.
@@ -298,6 +300,7 @@ static constexpr const auto KeyTbl = qMakeArray(
Xkb2Qt<XKB_KEY_XF86Book, Qt::Key_Book>,
Xkb2Qt<XKB_KEY_XF86CD, Qt::Key_CD>,
Xkb2Qt<XKB_KEY_XF86Calculater, Qt::Key_Calculator>,
+ Xkb2Qt<XKB_KEY_XF86Calculator, Qt::Key_Calculator>,
Xkb2Qt<XKB_KEY_XF86Clear, Qt::Key_Clear>,
Xkb2Qt<XKB_KEY_XF86ClearGrab, Qt::Key_ClearGrab>,
Xkb2Qt<XKB_KEY_XF86Close, Qt::Key_Close>,
@@ -488,9 +491,11 @@ int QXkbCommon::keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifie
// With standard shortcuts we should prefer a latin character, this is
// for checks like "some qkeyevent == QKeySequence::Copy" to work even
// when using for example 'russian' keyboard layout.
- xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code);
- if (latinKeysym != XKB_KEY_NoSymbol)
- keysym = latinKeysym;
+ if (!QXkbCommon::isLatin1(keysym)) {
+ xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code);
+ if (latinKeysym != XKB_KEY_NoSymbol)
+ keysym = latinKeysym;
+ }
}
return keysymToQtKey_internal(keysym, modifiers, state, code, superAsMeta, hyperAsMeta);
@@ -575,7 +580,7 @@ Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state, xkb_keysym_
if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0)
modifiers |= Qt::MetaModifier;
- if (keysym >= XKB_KEY_KP_Space && keysym <= XKB_KEY_KP_9)
+ if (isKeypad(keysym))
modifiers |= Qt::KeypadModifier;
return modifiers;
@@ -594,10 +599,24 @@ static const Qt::KeyboardModifiers ModsTbl[] = {
Qt::NoModifier // Fall-back to raw Key_*, for non-latin1 kb layouts
};
+/*
+ Compatibility until all sub modules have transitioned to new API below
+*/
QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
bool superAsMeta, bool hyperAsMeta)
{
QList<int> result;
+ auto keyCombinations = possibleKeyCombinations(state, event, superAsMeta, hyperAsMeta);
+ for (auto keyCombination : keyCombinations)
+ result << keyCombination.toCombined();
+
+ return result;
+}
+
+QList<QKeyCombination> QXkbCommon::possibleKeyCombinations(xkb_state *state, const QKeyEvent *event,
+ bool superAsMeta, bool hyperAsMeta)
+{
+ QList<QKeyCombination> result;
quint32 keycode = event->nativeScanCode();
if (!keycode)
return result;
@@ -611,7 +630,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
ScopedXKBState scopedXkbQueryState(xkb_state_new(keymap));
xkb_state *queryState = scopedXkbQueryState.get();
if (!queryState) {
- qCWarning(lcXkbcommon) << Q_FUNC_INFO << "failed to compile xkb keymap";
+ qCWarning(lcQpaKeyMapper) << Q_FUNC_INFO << "failed to compile xkb keymap";
return result;
}
// get kb state from the master state and update the temporary state
@@ -637,7 +656,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
int baseQtKey = keysymToQtKey_internal(sym, modifiers, queryState, keycode, superAsMeta, hyperAsMeta);
if (baseQtKey)
- result += (baseQtKey + int(modifiers));
+ result += QKeyCombination::fromCombined(baseQtKey + int(modifiers));
xkb_mod_index_t shiftMod = xkb_keymap_mod_get_index(keymap, "Shift");
xkb_mod_index_t altMod = xkb_keymap_mod_get_index(keymap, "Alt");
@@ -683,8 +702,9 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
// catch only more specific shortcuts, i.e. Ctrl+Shift+= also generates Ctrl++ and +,
// but Ctrl++ is more specific than +, so we should skip the last one
bool ambiguous = false;
- for (int shortcut : std::as_const(result)) {
- if (int(shortcut & ~Qt::KeyboardModifierMask) == qtKey && (shortcut & mods) == mods) {
+ for (auto keyCombination : std::as_const(result)) {
+ if (keyCombination.key() == qtKey
+ && (keyCombination.keyboardModifiers() & mods) == mods) {
ambiguous = true;
break;
}
@@ -692,7 +712,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event,
if (ambiguous)
continue;
- result += (qtKey + int(mods));
+ result += QKeyCombination::fromCombined(qtKey + int(mods));
}
}
@@ -724,18 +744,23 @@ void QXkbCommon::verifyHasLatinLayout(xkb_keymap *keymap)
// selected layouts is irrelevant. Properly functioning desktop environments
// handle this behind the scenes, even if no latin key based layout has been
// explicitly listed in the selected layouts.
- qCDebug(lcXkbcommon, "no keyboard layouts with latin keys present");
+ qCDebug(lcQpaKeyMapper, "no keyboard layouts with latin keys present");
}
xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode)
{
xkb_layout_index_t layout;
xkb_keysym_t sym = XKB_KEY_NoSymbol;
+ if (!state)
+ return sym;
xkb_keymap *keymap = xkb_state_get_keymap(state);
const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(keymap, keycode);
+ const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(state, keycode);
// Look at user layouts in the order in which they are defined in system
// settings to find a latin keysym.
for (layout = 0; layout < layoutCount; ++layout) {
+ if (layout == currentLayout)
+ continue;
const xkb_keysym_t *syms = nullptr;
xkb_level_index_t level = xkb_state_key_get_level(state, keycode, layout);
if (xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms) != 1)
@@ -746,6 +771,34 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco
}
}
+ if (sym == XKB_KEY_NoSymbol)
+ return sym;
+
+ xkb_mod_mask_t latchedMods = xkb_state_serialize_mods(state, XKB_STATE_MODS_LATCHED);
+ xkb_mod_mask_t lockedMods = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED);
+
+ // Check for uniqueness, consider the following setup:
+ // setxkbmap -layout us,ru,us -variant dvorak,, -option 'grp:ctrl_alt_toggle' (set 'ru' as active).
+ // In this setup, the user would expect to trigger a ctrl+q shortcut by pressing ctrl+<physical x key>,
+ // because "US dvorak" is higher up in the layout settings list. This check verifies that an obtained
+ // 'sym' can not be acquired by any other layout higher up in the user's layout list. If it can be acquired
+ // then the obtained key is not unique. This prevents ctrl+<physical q key> from generating a ctrl+q
+ // shortcut in the above described setup. We don't want ctrl+<physical x key> and ctrl+<physical q key> to
+ // generate the same shortcut event in this case.
+ const xkb_keycode_t minKeycode = xkb_keymap_min_keycode(keymap);
+ const xkb_keycode_t maxKeycode = xkb_keymap_max_keycode(keymap);
+ ScopedXKBState queryState(xkb_state_new(keymap));
+ for (xkb_layout_index_t prevLayout = 0; prevLayout < layout; ++prevLayout) {
+ xkb_state_update_mask(queryState.get(), 0, latchedMods, lockedMods, 0, 0, prevLayout);
+ for (xkb_keycode_t code = minKeycode; code < maxKeycode; ++code) {
+ xkb_keysym_t prevSym = xkb_state_key_get_one_sym(queryState.get(), code);
+ if (prevSym == sym) {
+ sym = XKB_KEY_NoSymbol;
+ break;
+ }
+ }
+ }
+
return sym;
}
@@ -765,7 +818,7 @@ void QXkbCommon::setXkbContext(QPlatformInputContext *inputContext, struct xkb_c
QMetaMethod method = inputContext->metaObject()->method(methodIndex);
Q_ASSERT(method.isValid());
if (!method.isValid())
- qCWarning(lcXkbcommon) << normalizedSignature << "not found on" << inputContextClassName;
+ qCWarning(lcQpaKeyMapper) << normalizedSignature << "not found on" << inputContextClassName;
return method;
}();
diff --git a/src/gui/platform/unix/qxkbcommon_p.h b/src/gui/platform/unix/qxkbcommon_p.h
index d27f965a81..a40d794451 100644
--- a/src/gui/platform/unix/qxkbcommon_p.h
+++ b/src/gui/platform/unix/qxkbcommon_p.h
@@ -23,12 +23,12 @@
#include <xkbcommon/xkbcommon.h>
+#include <qpa/qplatformkeymapper.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon)
-
class QEvent;
class QKeyEvent;
class QPlatformInputContext;
@@ -53,17 +53,58 @@ public:
static Qt::KeyboardModifiers modifiers(struct xkb_state *state, xkb_keysym_t keysym = XKB_KEY_VoidSymbol);
- static QList<int> possibleKeys(xkb_state *state, const QKeyEvent *event,
- bool superAsMeta = false, bool hyperAsMeta = false);
+ static QList<int> possibleKeys(xkb_state *state,
+ const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false);
+ static QList<QKeyCombination> possibleKeyCombinations(xkb_state *state,
+ const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false);
static void verifyHasLatinLayout(xkb_keymap *keymap);
static xkb_keysym_t lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode);
static bool isLatin1(xkb_keysym_t sym) {
- return sym <= 0xff;
+ return sym >= 0x20 && sym <= 0xff;
}
static bool isKeypad(xkb_keysym_t sym) {
- return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9;
+ switch (sym) {
+ case XKB_KEY_KP_Space:
+ case XKB_KEY_KP_Tab:
+ case XKB_KEY_KP_Enter:
+ case XKB_KEY_KP_F1:
+ case XKB_KEY_KP_F2:
+ case XKB_KEY_KP_F3:
+ case XKB_KEY_KP_F4:
+ case XKB_KEY_KP_Home:
+ case XKB_KEY_KP_Left:
+ case XKB_KEY_KP_Up:
+ case XKB_KEY_KP_Right:
+ case XKB_KEY_KP_Down:
+ case XKB_KEY_KP_Prior:
+ case XKB_KEY_KP_Next:
+ case XKB_KEY_KP_End:
+ case XKB_KEY_KP_Begin:
+ case XKB_KEY_KP_Insert:
+ case XKB_KEY_KP_Delete:
+ case XKB_KEY_KP_Equal:
+ case XKB_KEY_KP_Multiply:
+ case XKB_KEY_KP_Add:
+ case XKB_KEY_KP_Separator:
+ case XKB_KEY_KP_Subtract:
+ case XKB_KEY_KP_Decimal:
+ case XKB_KEY_KP_Divide:
+ case XKB_KEY_KP_0:
+ case XKB_KEY_KP_1:
+ case XKB_KEY_KP_2:
+ case XKB_KEY_KP_3:
+ case XKB_KEY_KP_4:
+ case XKB_KEY_KP_5:
+ case XKB_KEY_KP_6:
+ case XKB_KEY_KP_7:
+ case XKB_KEY_KP_8:
+ case XKB_KEY_KP_9:
+ return true;
+ default:
+ return false;
+ }
}
static void setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context);
diff --git a/src/gui/platform/wasm/qlocalfileapi.cpp b/src/gui/platform/wasm/qlocalfileapi.cpp
index bfcb5a5866..76b99361c4 100644
--- a/src/gui/platform/wasm/qlocalfileapi.cpp
+++ b/src/gui/platform/wasm/qlocalfileapi.cpp
@@ -12,26 +12,15 @@ std::string qtFilterListToFileInputAccept(const QStringList &filterList)
{
QStringList transformed;
for (const auto &filter : filterList) {
-
- emscripten::val::global("console").call<void>("log", filter.toStdString());
-
const auto type = Type::fromQt(filter);
if (type && type->accept()) {
const auto &extensions = type->accept()->mimeType().extensions();
- for (const auto &ext : extensions) {
- emscripten::val::global("console").call<void>("log",
- ext.value().toString().toStdString());
- }
-
std::transform(extensions.begin(), extensions.end(), std::back_inserter(transformed),
[](const Type::Accept::MimeType::Extension &extension) {
return extension.value().toString();
});
}
}
- for (const QString &tran : transformed) {
- emscripten::val::global("console").call<void>("log", tran.toStdString());
- }
return transformed.join(QStringLiteral(",")).toStdString();
}
@@ -39,13 +28,12 @@ std::optional<emscripten::val> qtFilterListToTypes(const QStringList &filterList
{
using namespace qstdweb;
using namespace emscripten;
-
auto types = emscripten::val::array();
for (const auto &fileFilter : filterList) {
auto type = Type::fromQt(fileFilter);
if (type) {
- auto jsType = val::object();
+ auto jsType = emscripten::val::object();
jsType.set("description", type->description().toString().toStdString());
if (type->accept()) {
jsType.set("accept", ([&mimeType = type->accept()->mimeType()]() {
@@ -190,7 +178,7 @@ Type::Accept::MimeType::Extension::fromQt(QStringView qtRepresentation)
emscripten::val makeOpenFileOptions(const QStringList &filterList, bool acceptMultiple)
{
auto options = emscripten::val::object();
- if (auto typeList = qtFilterListToTypes(filterList)) {
+ if (auto typeList = qtFilterListToTypes(filterList); typeList) {
options.set("types", std::move(*typeList));
options.set("excludeAcceptAllOption", true);
}
diff --git a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
index 80e32d629d..a946cda043 100644
--- a/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
+++ b/src/gui/platform/wasm/qwasmlocalfileaccess.cpp
@@ -135,66 +135,18 @@ void readFiles(const qstdweb::FileList &fileList,
(*readFile)(0);
}
-QStringList acceptListFromQtFormat(const std::string &qtAcceptList)
+QStringList makeFilterList(const std::string &qtAcceptList)
{
// copy of qt_make_filter_list() from qfiledialog.cpp
- auto make_filter_list = [](const QString &filter) -> QStringList
- {
- if (filter.isEmpty())
- return QStringList();
-
- QString sep(";;");
- if (!filter.contains(sep) && filter.contains(u'\n'))
- sep = u'\n';
-
- return filter.split(sep);
- };
-
- const QStringList fileFilter = make_filter_list(QString::fromStdString(qtAcceptList));
- QStringList transformed;
- for (const auto &element : fileFilter) {
- // Accepts either a string in format:
- // GROUP3
- // or in this format:
- // GROUP1 (GROUP2)
- // Group 1 is treated as the description, whereas group 2 or 3 are treated as the filter
- // list.
- static QRegularExpression regex(
- QString(QStringLiteral("(?:([^(]*)\\(([^()]+)\\)[^)]*)|([^()]+)")));
- static QRegularExpression wordCharacterRegex(QString(QStringLiteral("\\w")));
- const auto match = regex.match(element);
-
- if (!match.hasMatch())
- continue;
-
- constexpr size_t FilterListFromParensIndex = 2;
- constexpr size_t PlainFilterListIndex = 3;
- QString filterList = match.captured(match.hasCaptured(FilterListFromParensIndex)
- ? FilterListFromParensIndex
- : PlainFilterListIndex);
- for (auto singleExtension : filterList.split(QStringLiteral(" "), Qt::SkipEmptyParts)) {
- // Checks for a filter that matches everything:
- // Any number of asterisks or any number of asterisks with a '.' between them.
- // The web filter does not support wildcards.
- static QRegularExpression qtAcceptAllRegex(QRegularExpression::anchoredPattern(
- QString(QStringLiteral("[*]+|[*]+\\.[*]+"))));
- if (qtAcceptAllRegex.match(singleExtension).hasMatch())
- continue;
-
- // Checks for correctness. The web filter only allows filename extensions and does not
- // filter the actual filenames, therefore we check whether the filter provided only
- // filters for the extension.
- static QRegularExpression qtFilenameMatcherRegex(QRegularExpression::anchoredPattern(
- QString(QStringLiteral("(\\*?)(\\.[^*]+)"))));
-
- auto extensionMatch = qtFilenameMatcherRegex.match(singleExtension);
- if (extensionMatch.hasMatch())
- transformed.append(extensionMatch.captured(2));
- }
- }
- return transformed;
+ auto filter = QString::fromStdString(qtAcceptList);
+ if (filter.isEmpty())
+ return QStringList();
+ QString sep(";;");
+ if (!filter.contains(sep) && filter.contains(u'\n'))
+ sep = u'\n';
+
+ return filter.split(sep);
}
-
}
void downloadDataAsFile(const char *content, size_t size, const std::string &fileNameHint)
@@ -225,7 +177,7 @@ void openFiles(const std::string &accept, FileSelectMode fileSelectMode,
const std::function<char *(uint64_t size, const std::string& name)> &acceptFile,
const std::function<void()> &fileDataReady)
{
- FileDialog::showOpen(acceptListFromQtFormat(accept), fileSelectMode, {
+ FileDialog::showOpen(makeFilterList(accept), fileSelectMode, {
.thenFunc = [=](emscripten::val result) {
auto files = qstdweb::FileList(result);
fileDialogClosed(files.length());
diff --git a/src/gui/platform/windows/qwindowsguieventdispatcher.cpp b/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
index f70655380d..c2f0efe96e 100644
--- a/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
+++ b/src/gui/platform/windows/qwindowsguieventdispatcher.cpp
@@ -197,3 +197,5 @@ const char *QWindowsGuiEventDispatcher::windowsMessageName(UINT msg)
}
QT_END_NAMESPACE
+
+#include "moc_qwindowsguieventdispatcher_p.cpp"
diff --git a/src/gui/platform/windows/qwindowsmimeconverter.cpp b/src/gui/platform/windows/qwindowsmimeconverter.cpp
index d7998a3eb7..49d524cb99 100644
--- a/src/gui/platform/windows/qwindowsmimeconverter.cpp
+++ b/src/gui/platform/windows/qwindowsmimeconverter.cpp
@@ -25,6 +25,22 @@ QT_BEGIN_NAMESPACE
conversions between Windows Clipboard and MIME formats, you can convert
proprietary clipboard formats to MIME formats.
+ Construct an instance of your converter implementation after instantiating
+ QGuiApplication:
+
+ \code
+ int main(int argc, char **argv)
+ {
+ QGuiApplication app(argc, argv);
+ JsonMimeConverter jsonConverter;
+ }
+ \endcode
+
+ Destroying the instance will unregister the converter and remove support
+ for the conversion. It is also valid to heap-allocate the converter
+ instance; Qt takes ownership and will delete the converter object during
+ QGuiApplication shut-down.
+
Qt has predefined support for the following Windows Clipboard formats:
\table
@@ -112,6 +128,8 @@ QT_BEGIN_NAMESPACE
The instance is automatically registered, and will be called to convert data during
clipboard or drag'n'drop operations.
+
+ Call this constructor after QGuiApplication has been created.
*/
QWindowsMimeConverter::QWindowsMimeConverter()
{
diff --git a/src/gui/platform/windows/qwindowsnativeinterface.cpp b/src/gui/platform/windows/qwindowsnativeinterface.cpp
index 86c6593f5d..44f230e1d3 100644
--- a/src/gui/platform/windows/qwindowsnativeinterface.cpp
+++ b/src/gui/platform/windows/qwindowsnativeinterface.cpp
@@ -89,14 +89,20 @@ QOpenGLContext *QNativeInterface::QWGLContext::fromNative(HGLRC context, HWND wi
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsApplication);
/*!
- \class QNativeInterface::Private::QWindowsScreen
- \since 6.5
- \internal
- \brief Native interface to QScreen, to be retrieved from QPlatformIntegration.
+ \class QNativeInterface::QWindowsScreen
+ \since 6.7
+ \brief Native interface to a screen.
+
+ Accessed through QScreen::nativeInterface().
\inmodule QtGui
\ingroup native-interfaces
+ \ingroup native-interfaces-qscreen
*/
-QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsScreen);
+/*!
+ * \fn HWMONITOR QNativeInterface::QWindowsScreen::handle() const;
+ * \return The underlying HWMONITOR of the screen.
+ */
+QT_DEFINE_NATIVE_INTERFACE(QWindowsScreen);
/*!
\enum QNativeInterface::Private::QWindowsApplication::TouchWindowTouchType
@@ -175,15 +181,7 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsScreen);
\value DarkModeStyle The Windows Vista style will be turned off and
a simple dark style will be used.
- \sa isDarkMode(), setDarkModeHandling()
-*/
-
-/*!
- \fn bool QNativeInterface::Private::QWindowsApplication::isDarkMode() const = 0
- \internal
-
- Returns \c true if Windows 10 is configured to use dark mode for
- applications.
+ \sa setDarkModeHandling()
*/
/*!
diff --git a/src/gui/platform/windows/qwindowsthemecache.cpp b/src/gui/platform/windows/qwindowsthemecache.cpp
new file mode 100644
index 0000000000..3bb92e67ca
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsthemecache_p.h"
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+// Theme names matching the QWindowsVistaStylePrivate::Theme enumeration.
+constexpr const wchar_t *themeNames[] = {
+ L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW",
+ L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN",
+ L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR",
+ L"WINDOW", L"STATUS", L"TREEVIEW"
+};
+
+typedef std::array<HTHEME, std::size(themeNames)> ThemeArray;
+typedef QHash<HWND, ThemeArray> ThemesCache;
+Q_GLOBAL_STATIC(ThemesCache, themesCache);
+
+QString QWindowsThemeCache::themeName(int theme)
+{
+ return theme >= 0 && theme < int(std::size(themeNames))
+ ? QString::fromWCharArray(themeNames[theme]) : QString();
+}
+
+HTHEME QWindowsThemeCache::createTheme(int theme, HWND hwnd)
+{
+ if (Q_UNLIKELY(theme < 0 || theme >= int(std::size(themeNames)) || !hwnd)) {
+ qWarning("Invalid parameters #%d, %p", theme, hwnd);
+ return nullptr;
+ }
+
+ // Get or create themes array for this window.
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ it = cache->insert(hwnd, ThemeArray {});
+
+ // Get or create theme data
+ ThemeArray &themes = *it;
+ if (!themes[theme]) {
+ const wchar_t *name = themeNames[theme];
+ themes[theme] = OpenThemeData(hwnd, name);
+ if (Q_UNLIKELY(!themes[theme]))
+ qErrnoWarning("OpenThemeData() failed for theme %d (%s).",
+ theme, qPrintable(themeName(theme)));
+ }
+ return themes[theme];
+}
+
+static void clearThemes(ThemeArray &themes)
+{
+ for (auto &theme : themes) {
+ if (theme) {
+ CloseThemeData(theme);
+ theme = nullptr;
+ }
+ }
+}
+
+void QWindowsThemeCache::clearThemeCache(HWND hwnd)
+{
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ return;
+ clearThemes(*it);
+}
+
+void QWindowsThemeCache::clearAllThemeCaches()
+{
+ ThemesCache *cache = themesCache();
+ for (auto &themeArray : *cache)
+ clearThemes(themeArray);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/windows/qwindowsthemecache_p.h b/src/gui/platform/windows/qwindowsthemecache_p.h
new file mode 100644
index 0000000000..beb724dc5c
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache_p.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSTHEME_CACHE_P_H
+#define QWINDOWSTHEME_CACHE_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 "QtGui/private/qtguiglobal_p.h"
+
+#include <QtCore/qt_windows.h>
+#include <uxtheme.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QWindowsThemeCache
+{
+ Q_GUI_EXPORT QString themeName(int theme);
+ Q_GUI_EXPORT HTHEME createTheme(int theme, HWND hwnd);
+ Q_GUI_EXPORT void clearThemeCache(HWND hwnd);
+ Q_GUI_EXPORT void clearAllThemeCaches();
+}
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSTHEME_CACHE_P_H
diff --git a/src/gui/qt_cmdline.cmake b/src/gui/qt_cmdline.cmake
index 379cc417e7..446618ebc4 100644
--- a/src/gui/qt_cmdline.cmake
+++ b/src/gui/qt_cmdline.cmake
@@ -27,7 +27,8 @@ qt_commandline_option(opengl TYPE optionalString VALUES no yes desktop es2 dynam
qt_commandline_option(opengl-es-2 TYPE void NAME opengl VALUE es2)
qt_commandline_option(opengles3 TYPE boolean)
qt_commandline_option(openvg TYPE boolean)
-qt_commandline_option(qpa TYPE string NAME qpa_default_platform)
+qt_commandline_option(qpa TYPE string NAME qpa_platforms)
+qt_commandline_option(default-qpa TYPE string NAME qpa_default_platform)
qt_commandline_option(sm TYPE boolean NAME sessionmanager)
qt_commandline_option(tslib TYPE boolean)
qt_commandline_option(vulkan TYPE boolean)
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 58bbc34941..a39709c726 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -16,7 +16,7 @@
#include "qrhid3d11_p.h"
#include "qrhid3d12_p.h"
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
#include "qrhimetal_p.h"
#endif
@@ -29,7 +29,8 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
/*!
\class QRhi
\ingroup painting-3D
- \inmodule QtGui
+ \inmodule QtGuiPrivate
+ \inheaderfile rhi/qrhi.h
\since 6.6
\brief Accelerated 2D/3D graphics API abstraction.
@@ -71,19 +72,22 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
builds on QOpenGLContext, QOpenGLFunctions, and the related cross-platform
infrastructure of the Qt GUI module.
- \li Direct3D 11.1 or newer, with Shader Model 5.0 or newer . When the D3D
+ \li Direct3D 11.1 or newer, with Shader Model 5.0 or newer. When the D3D
runtime has no support for 11.1 features or Shader Model 5.0,
initialization using an accelerated graphics device will fail, but using
the
\l{https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp}{software
adapter} is still an option.
- \li Direct3D 12.0 or newer. The D3D12 device is by default created with
- specifying a minimum feature level of \c{D3D_FEATURE_LEVEL_11_0}.
+ \li Direct3D 12 on Windows 10 version 1703 and newer, with Shader Model 5.0
+ or newer. Qt requires ID3D12Device2 to be present, hence the requirement
+ for at least version 1703 of Windows 10. The D3D12 device is by default
+ created with specifying a minimum feature level of
+ \c{D3D_FEATURE_LEVEL_11_0}.
\li Metal 1.2 or newer.
- \li Vulkan 1.0 or newer , optionally utilizing some Vulkan 1.1 level
+ \li Vulkan 1.0 or newer, optionally utilizing some Vulkan 1.1 level
features.
\li Null, a "dummy" backend that issues no graphics calls at all.
@@ -650,7 +654,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
mechanism because the cost of maintaining the related data structures is
not insignificant with some backends. With Vulkan this feature maps
directly to VkPipelineCache, vkGetPipelineCacheData and
- VkPipelineCacheCreateInfo::pInitialData. With D3D11 there is no real
+ VkPipelineCacheCreateInfo::pInitialData. With Direct3D 11 there is no real
pipline cache, but the results of HLSL->DXBC compilations are stored and
can be serialized/deserialized via this mechanism. This allows skipping the
time consuming D3DCompile() in future runs of the applications for shaders
@@ -662,6 +666,17 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
mechanisms for shader/program binaries provided by Qt. Writing to those may
get disabled whenever this flag is set since storing program binaries to
multiple caches is not sensible.
+
+ \value SuppressSmokeTestWarnings Indicates that, with backends where this
+ is relevant, certain, non-fatal QRhi::create() failures should not
+ produce qWarning() calls. For example, with D3D11, passing this flag
+ makes a number of warning messages (that appear due to QRhi::create()
+ failing) to become categorized debug prints instead under the commonly used
+ \c{qt.rhi.general} logging category. This can be used by engines, such as
+ Qt Quick, that feature fallback logic, i.e. they retry calling create()
+ with a different set of flags (such as, \l PreferSoftwareRenderer), in order
+ to hide the unconditional warnings from the output that would be printed
+ when the first create() attempt had failed.
*/
/*!
@@ -699,9 +714,12 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
QRhiCommandBuffer::debugMarkBegin()) are supported.
\value Timestamps Indicates that command buffer timestamps are supported.
- Relevant for QRhiCommandBuffer::lastCompletedGpuTime(). Can be expected to
- be supported on Metal, Vulkan, and Direct 3D, assuming the underlying
- implementation supports timestamp queries or similar.
+ Relevant for QRhiCommandBuffer::lastCompletedGpuTime(). This can be
+ expected to be supported on Metal, Vulkan, Direct 3D 11 and 12, and OpenGL
+ contexts of version 3.3 or newer. However, with some of these APIs support
+ for timestamp queries is technically optional, and therefore it cannot be
+ guaranteed that this feature is always supported with every implementation
+ of them.
\value Instancing Indicates that instanced drawing is supported. In
practice this feature will be unsupported with OpenGL ES 2.0 and OpenGL
@@ -771,7 +789,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\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
+ backends (D3D11, D3D12, Metal). With Vulkan, the value depends on the
implementation. With OpenGL, wide lines are not supported in core profile
contexts.
@@ -932,7 +950,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
with image load/store. This feature is only available with some backends as
it does not map well to all graphics APIs, and it is only meant to provide
support for special cases anyhow. In practice the feature can be expected to
- be supported with Direct3D 11 and Vulkan.
+ be supported with Direct3D 11/12 and Vulkan.
\value NonFillPolygonMode Indicates that setting a PolygonMode other than
the default Fill is supported for QRhiGraphicsPipeline. A common use case
@@ -953,8 +971,8 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
When not supported, build() will succeed but just show a warning message
and the values of the target attributes will be broken. In practice this
feature will be unsupported in some OpenGL ES 2.0 and OpenGL 2.x
- implementations. Note that while D3D does support half precision input
- attributes, it does not support the half3 type. The D3D backends pass
+ implementations. Note that while Direct3D 11/12 does support half precision
+ input attributes, it does not support the half3 type. The D3D backends pass
half3 attributes as half4. To ensure cross platform compatibility, half3
inputs should be padded to 8 bytes.
@@ -966,6 +984,58 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\value ThreeDimensionalTextureMipmaps Indicates that generating 3D texture
mipmaps are supported. In practice this feature will be unsupported with
Direct 3D 12.
+
+ \value MultiView Indicates that multiview, see e.g.
+ \l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_multiview.html}{VK_KHR_multiview}
+ is supported. With OpenGL ES 2.0, Direct 3D 11, and OpenGL (ES)
+ implementations without \c{GL_OVR_multiview2} this feature will not be
+ supported. With Vulkan 1.1 and newer, and Direct 3D 12 multiview is
+ typically supported. When reported as supported, creating a
+ QRhiTextureRenderTarget with a QRhiColorAttachment that references a texture
+ array and has \l{QRhiColorAttachment::setMultiViewCount()}{multiViewCount}
+ set enables recording a render pass that uses multiview rendering. In addition,
+ any QRhiGraphicsPipeline used in that render pass must have
+ \l{QRhiGraphicsPipeline::setMultiViewCount()}{the same view count set}. Note that
+ multiview is only available in combination with 2D texture arrays. It cannot
+ be used to optimize the rendering into individual textures (e.g. two, for
+ the left and right eyes). Rather, the target of a multiview render pass is
+ always a texture array, automatically rendering to the layer (array element)
+ corresponding to each view. Therefore this feature implies \l TextureArrays
+ as well. Multiview rendering is not supported in combination with
+ tessellation or geometry shaders. See QRhiColorAttachment::setMultiViewCount()
+ for further details on multiview rendering. This enum value has been introduced in Qt 6.7.
+
+ \value TextureViewFormat Indicates that setting a
+ \l{QRhiTexture::setWriteViewFormat()}{view format} on a QRhiTexture is
+ effective. When reported as supported, setting the read (sampling) or write
+ (render target / image load-store) view mode changes the texture's viewing
+ format. When unsupported, setting a view format has no effect. Note that Qt
+ has no knowledge or control over format compatibility or resource view rules
+ in the underlying 3D API and its implementation. Passing in unsuitable,
+ incompatible formats may lead to errors and unspecified behavior. This is
+ provided mainly to allow "casting" rendering into a texture created with an
+ sRGB format to non-sRGB to avoid the unwanted linear->sRGB conversion on
+ shader writes. Other types of casting may or may not be functional,
+ depending on the underlying API. Currently implemented for Vulkan and Direct
+ 3D 12. With D3D12 the feature is available only if
+ \c CastingFullyTypedFormatSupported is supported, see
+ \l{https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html} (and
+ note that QRhi always uses fully typed formats for textures.) This enum
+ value has been introduced in Qt 6.8.
+
+ \value ResolveDepthStencil Indicates that resolving a multisample depth or
+ depth-stencil texture is supported. Otherwise,
+ \l{QRhiTextureRenderTargetDescription::setDepthResolveTexture()}{setting a
+ depth resolve texture} is not functional and must be avoided. Direct 3D 11
+ and 12 have no support for resolving depth/depth-stencil formats, and
+ therefore this feature will never be supported with those. Vulkan 1.0 has no
+ API to request resolving a depth-stencil attachment. Therefore, with Vulkan
+ this feature will only be supported with Vulkan 1.2 and up, and on 1.1
+ implementations with the appropriate extensions present. This feature is
+ provided for the rare case when resolving into a non-multisample depth
+ texture becomes necessary, for example when rendering into an
+ OpenXR-provided depth texture (XR_KHR_composition_layer_depth). This enum
+ value has been introduced in Qt 6.8.
*/
/*!
@@ -1083,7 +1153,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
Contains fields that are relevant to all backends.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -1093,7 +1163,7 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\since 6.6
\brief Specifies clear values for a depth or stencil buffer.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -1188,7 +1258,7 @@ QDebug operator<<(QDebug dbg, const QRhiDepthStencilClearValue &v)
// ...
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiCommandBuffer::setViewport(), QRhi::clipSpaceCorrMatrix(), QRhiScissor
@@ -1308,7 +1378,7 @@ QDebug operator<<(QDebug dbg, const QRhiViewport &v)
appropriate. Therefore, any rendering logic targeting OpenGL can feed
scissor rectangles into QRhiScissor as-is, without any adaptation.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiCommandBuffer::setScissor(), QRhiViewport
@@ -1436,7 +1506,7 @@ QDebug operator<<(QDebug dbg, const QRhiScissor &s)
\note the stride must always be a multiple of 4.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiCommandBuffer::setVertexInput()
@@ -1605,7 +1675,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b)
cb->setVertexInput(0, 1, &vbufBinding);
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiCommandBuffer::setVertexInput()
@@ -1634,12 +1704,22 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputBinding &b)
\value Half3 Three component half precision (16 bit) float vector
\value Half2 Two component half precision (16 bit) float vector
\value Half Half precision (16 bit) float
+ \value UShort4 Four component unsigned short (16 bit) integer vector
+ \value UShort3 Three component unsigned short (16 bit) integer vector
+ \value UShort2 Two component unsigned short (16 bit) integer vector
+ \value UShort Unsigned short (16 bit) integer
+ \value SShort4 Four component signed short (16 bit) integer vector
+ \value SShort3 Three component signed short (16 bit) integer vector
+ \value SShort2 Two component signed short (16 bit) integer vector
+ \value SShort Signed short (16 bit) integer
\note Support for half precision floating point attributes is indicated at
- run time by the QRhi::Feature::HalfAttributes feature flag. Note that D3D
- supports half input attributes, but does not support the Half3 type. The
- D3D backends pass through Half3 as Half4. To ensure cross platform
- compatibility, Half3 inputs should be padded to 8 bytes.
+ run time by the QRhi::Feature::HalfAttributes feature flag.
+
+ \note Direct3D 11/12 supports 16 bit input attributes, but does not support
+ the Half3, UShort3 or SShort3 types. The D3D backends pass through Half3 as
+ Half4, UShort3 as UShort4, and SShort3 as SShort4. To ensure cross platform
+ compatibility, 16 bit inputs should be padded to 8 bytes.
*/
/*!
@@ -1851,6 +1931,24 @@ quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInpu
case QRhiVertexInputAttribute::Half:
return sizeof(qfloat16);
+ case QRhiVertexInputAttribute::UShort4:
+ return 4 * sizeof(quint16);
+ case QRhiVertexInputAttribute::UShort3:
+ return 4 * sizeof(quint16); // ivec3 still takes 8 bytes
+ case QRhiVertexInputAttribute::UShort2:
+ return 2 * sizeof(quint16);
+ case QRhiVertexInputAttribute::UShort:
+ return sizeof(quint16);
+
+ case QRhiVertexInputAttribute::SShort4:
+ return 4 * sizeof(qint16);
+ case QRhiVertexInputAttribute::SShort3:
+ return 4 * sizeof(qint16); // uvec3 still takes 8 bytes
+ case QRhiVertexInputAttribute::SShort2:
+ return 2 * sizeof(qint16);
+ case QRhiVertexInputAttribute::SShort:
+ return sizeof(qint16);
+
default:
Q_UNREACHABLE_RETURN(1);
}
@@ -1883,7 +1981,7 @@ quint32 QRhiImplementation::byteSizePerVertexForVertexInputFormat(QRhiVertexInpu
});
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -2006,9 +2104,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v)
QShader getShader(const QString &name)
{
QFile f(name);
- if (f.open(QIODevice::ReadOnly))
- return QShader::fromSerialized(f.readAll());
- return QShader();
+ return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader();
}
QShader vs = getShader("material.vert.qsb");
@@ -2019,7 +2115,7 @@ QDebug operator<<(QDebug dbg, const QRhiVertexInputLayout &v)
});
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -2169,7 +2265,7 @@ QDebug operator<<(QDebug dbg, const QRhiShaderStage &s)
out at all. This means that the multisample texture() must not be used
afterwards with shaders for sampling when resolveTexture() is set.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiTextureRenderTargetDescription
@@ -2263,16 +2359,40 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
\nullptr if there is none.
Setting a non-null resolve texture is applicable when the attachment
- references a multisample, color renderbuffer. (i.e., renderBuffer() is set)
- The QRhiTexture in the resolveTexture() is then a regular, 2D,
- non-multisample texture with the same size (but a sample count of 1). The
- multisample content is automatically resolved into this texture at the end
- of each render pass.
+ references a multisample texture or renderbuffer. The QRhiTexture in the
+ resolveTexture() is then a non-multisample 2D texture (or texture array)
+ with the same size (but a sample count of 1). The multisample content is
+ automatically resolved into this texture at the end of each render pass.
*/
/*!
\fn void QRhiColorAttachment::setResolveTexture(QRhiTexture *tex)
+
Sets the resolve texture \a tex.
+
+ \a tex is expected to be a 2D texture or a 2D texture array. In either
+ case, resolving targets a single mip level of a single layer (array
+ element) of \a tex. The mip level and array layer are specified by
+ resolveLevel() and resolveLayer().
+
+ An exception is \l{setMultiViewCount()}{multiview}: when the color
+ attachment is associated with a texture array and multiview is enabled, the
+ resolve texture must also be a texture array with sufficient elements for
+ all views. In this case all elements that correspond to views are resolved
+ automatically; the behavior is similar to the following pseudo-code:
+ \badcode
+ for (i = 0; i < multiViewCount(); ++i)
+ resolve texture's layer() + i into resolveTexture's resolveLayer() + i
+ \endcode
+
+ Setting a non-multisample texture to resolve a multisample texture or
+ renderbuffer automatically at the end of the render pass is often
+ preferable to working with multisample textures (and not setting a resolve
+ texture), because it avoids the need for writing dedicated fragment shaders
+ that work exclusively with multisample textures (\c sampler2DMS, \c
+ texelFetch, etc.), and rather allows using the same shader as one would if
+ the attachment's texture was not multisampled to begin with. This comes at
+ the expense of an additional resource (the non-multisample \a tex).
*/
/*!
@@ -2296,6 +2416,71 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
*/
/*!
+ \fn int QRhiColorAttachment::multiViewCount() const
+
+ \return the currently set number of views. Defaults to 0 which indicates
+ the render target with this color attachment is not going to be used with
+ multiview rendering.
+
+ \since 6.7
+ */
+
+/*!
+ \fn void QRhiColorAttachment::setMultiViewCount(int count)
+
+ Sets the view \a count. Setting a value larger than 1 indicates that the
+ render target with this color attachment is going to be used with multiview
+ rendering. The default value is 0. Values smaller than 2 indicate no
+ multiview rendering.
+
+ When \a count is set to \c 2 or greater, the color attachment must be
+ associated with a 2D texture array. layer() and multiViewCount() together
+ define the range of texture array elements that are targeted during
+ multiview rendering.
+
+ For example, if \c layer is \c 0 and \c multiViewCount is \c 2, the texture
+ array must have 2 (or more) elements, and the multiview rendering will
+ target elements 0 and 1. The \c{gl_ViewIndex} variable in the shaders has a
+ value of \c 0 or \c 1 then, where view \c 0 corresponds to the texture array
+ element \c 0, and view \c 1 to the array element \c 1.
+
+ \note Setting a \a count larger than 1, using a texture array as texture(),
+ and calling \l{QRhiCommandBuffer::beginPass()}{beginPass()} on a
+ QRhiTextureRenderTarget with this color attachment implies multiview
+ rendering for the entire render pass. multiViewCount() should not be set
+ unless multiview rendering is wanted. Multiview cannot be used with texture
+ types other than 2D texture arrays. (although 3D textures may work,
+ depending on the graphics API and backend; applications are nonetheless
+ advised not to rely on that and only use 2D texture arrays as the render
+ targets of multiview rendering)
+
+ See
+ \l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview}
+ for more details regarding multiview rendering. Do note that Qt requires
+ \l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview2.txt}{GL_OVR_multiview2}
+ as well, when running on OpenGL (ES).
+
+ Multiview rendering is available only when the
+ \l{QRhi::MultiView}{MultiView} feature is reported as supported from
+ \l{QRhi::isFeatureSupported()}{isFeatureSupported()}.
+
+ \note For portability, be aware of limitations that exist for multiview
+ rendering with some of the graphics APIs. It is recommended that multiview
+ render passes do not rely on any of the features that
+ \l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview}
+ declares as unsupported. The one exception is shader stage outputs other
+ than \c{gl_Position} depending on \c{gl_ViewIndex}: that can be relied on
+ (even with OpenGL) because QRhi never reports multiview as supported without
+ \c{GL_OVR_multiview2} also being present.
+
+ \note Multiview rendering is not supported in combination with tessellation
+ or geometry shaders, even though some implementations of some graphics APIs
+ may allow this.
+
+ \since 6.7
+ */
+
+/*!
\class QRhiTextureRenderTargetDescription
\inmodule QtGui
\since 6.6
@@ -2371,7 +2556,26 @@ QRhiColorAttachment::QRhiColorAttachment(QRhiRenderBuffer *renderBuffer)
QRhiTextureRenderTarget *rt = rhi->newTextureRenderTarget({ colorAtt, depthStencil });
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note when multisample resolving is enabled, the multisample data may not be
+ written out at all. This means that the multisample texture in a color
+ attachment must not be used afterwards with shaders for sampling (or other
+ purposes) whenever a resolve texture is set, since the multisample color
+ buffer is merely an intermediate storage then that gets no data written back
+ on some GPU architectures at all. See
+ \l{QRhiTextureRenderTarget::Flag}{PreserveColorContents} for more details.
+
+ \note When using setDepthTexture(), not setDepthStencilBuffer(), and the
+ depth (stencil) data is not of interest afterwards, set the
+ DoNotStoreDepthStencilContents flag on the QRhiTextureRenderTarget. This
+ allows indicating to the underlying 3D API that the depth/stencil data can
+ be discarded, leading potentially to better performance with tiled GPU
+ architectures. When the depth-stencil buffer is a QRhiRenderBuffer (and also
+ for the multisample color texture, see previous note) this is implicit, but
+ with a depth (stencil) QRhiTexture the intention needs to be declared
+ explicitly. By default QRhi assumes that the data is of interest (e.g., the
+ depth texture is sampled in a shader afterwards).
+
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiColorAttachment, QRhiTextureRenderTarget
@@ -2464,6 +2668,16 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh
\note depthStencilBuffer() and depthTexture() cannot be both set (cannot be
non-null at the same time).
+
+ Using a QRhiRenderBuffer over a 2D QRhiTexture as the depth or
+ depth/stencil buffer is very common, and is the recommended approach for
+ applications. Using a QRhiTexture, and so setDepthTexture() becomes
+ relevant if the depth data is meant to be accessed (e.g. sampled in a
+ shader) afterwards, or when
+ \l{QRhiColorAttachment::setMultiViewCount()}{multiview rendering} is
+ involved (because then the depth texture must be a texture array).
+
+ \sa setDepthTexture()
*/
/*!
@@ -2480,6 +2694,49 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh
\note depthStencilBuffer() and depthTexture() cannot be both set (cannot be
non-null at the same time).
+
+ \a texture can either be a 2D texture or a 2D texture array (when texture
+ arrays are supported). Specifying a texture array is relevant in particular
+ with
+ \l{QRhiColorAttachment::setMultiViewCount()}{multiview rendering}.
+
+ \note If \a texture is a format with a stencil component, such as
+ \l QRhiTexture::D24S8, it will serve as the stencil buffer as well.
+
+ \sa setDepthStencilBuffer()
+ */
+
+/*!
+ \fn QRhiTexture *QRhiTextureRenderTargetDescription::depthResolveTexture() const
+
+ \return the texture to which a multisample depth (or depth-stencil) texture
+ (or texture array) is resolved to. \nullptr if there is none, which is the
+ most common case.
+
+ \since 6.8
+ \sa QRhiColorAttachment::resolveTexture(), depthTexture()
+ */
+
+/*!
+ \fn void QRhiTextureRenderTargetDescription::setDepthResolveTexture(QRhiTexture *tex)
+
+ Sets the depth (or depth-stencil) resolve texture \a tex.
+
+ \a tex is expected to be a 2D texture or a 2D texture array with a format
+ matching the texture set via setDepthTexture().
+
+ \note Resolving depth (or depth-stencil) data is only functional when the
+ \l ResolveDepthStencil feature is reported as supported at run time. Support
+ for depth-stencil resolve is not universally available among the graphics
+ APIs. Designs assuming unconditional availability of depth-stencil resolve
+ are therefore non-portable, and should be avoided.
+
+ \note As an additional limitation for OpenGL ES in particular, setting a
+ depth resolve texture may only be functional in combination with
+ setDepthTexture(), not with setDepthStencilBuffer().
+
+ \since 6.8
+ \sa QRhiColorAttachment::setResolveTexture(), setDepthTexture()
*/
/*!
@@ -2543,7 +2800,7 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh
caller is strongly encouraged to call QImage::detach() on the image before
passing it to uploadTexture().
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiTextureUploadDescription
@@ -2606,6 +2863,7 @@ QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription
\fn void QRhiTextureSubresourceUploadDescription::setImage(const QImage &image)
Sets \a image.
+ Upon textures loading, the image data will be read as is, with no formats conversions.
\note image() and data() cannot be both set at the same time.
*/
@@ -2698,7 +2956,7 @@ QRhiTextureSubresourceUploadDescription::QRhiTextureSubresourceUploadDescription
\brief Describes one layer (face for cubemaps, slice for 3D textures,
element for texture arrays) in a texture upload operation.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -2838,7 +3096,7 @@ QRhiTextureUploadEntry::QRhiTextureUploadEntry(int layer, int level,
resourceUpdates->uploadTexture(texture, desc);
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiResourceUpdateBatch
@@ -2927,7 +3185,7 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list
differ, but the size and position must be carefully controlled to avoid out
of bounds copies, in which case the behavior is undefined.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3033,7 +3291,7 @@ QRhiTextureUploadDescription::QRhiTextureUploadDescription(std::initializer_list
\note Multisample textures cannot be read back. Readbacks are supported for
multisample swapchain buffers however.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3124,7 +3382,7 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
available. \l format and \l pixelSize are set upon completion together with
\l data.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3162,7 +3420,7 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
\since 6.6
\brief Base class for classes exposing backend-specific collections of native resource objects.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3172,7 +3430,7 @@ QRhiReadbackDescription::QRhiReadbackDescription(QRhiTexture *texture)
\since 6.6
\brief Base class for classes encapsulating native resource objects.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3353,7 +3611,7 @@ QRhi *QRhiResource::rhi() const
\since 6.6
\brief Vertex, index, or uniform (constant) buffer resource.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A QRhiBuffer encapsulates zero, one, or more native buffer objects (such as
@@ -3460,7 +3718,7 @@ QRhi *QRhiResource::rhi() const
QRhiResourceUpdateBatch *batch = rhi->nextResourceUpdateBatch();
for (int i = 0; i < N; ++i) {
batch->updateDynamicBuffer(ubuf, i * ONE_UBUF_SIZE, 64, matrix.constData());
- updates->updateDynamicBuffer(ubuf, i * ONE_UBUF_SIZE + 64, 4, &opacity);
+ batch->updateDynamicBuffer(ubuf, i * ONE_UBUF_SIZE + 64, 4, &opacity);
}
// ...
// beginPass(), set pipeline, etc., and then:
@@ -3541,10 +3799,13 @@ QRhi *QRhiResource::rhi() const
objects array are pointers to a GLuint. With Vulkan, the native handle is a
VkBuffer, so the elements of the array are pointers to a VkBuffer. With
Direct3D 11 and Metal the elements are pointers to a ID3D11Buffer or
- MTLBuffer pointer, respectively.
+ MTLBuffer pointer, respectively. With Direct3D 12, the elements are
+ pointers to a ID3D12Resource.
\note Pay attention to the fact that the elements are always pointers to
the native buffer handle type, even if the native type itself is a pointer.
+ (so the elements are \c{VkBuffer *} on Vulkan, even though VkBuffer itself
+ is a pointer on 64-bit architectures).
*/
/*!
@@ -3748,7 +4009,7 @@ void QRhiBuffer::endFullDynamicBufferUpdateForCurrentFrame()
means calling setPixelSize() and create() are not necessary for such
renderbuffers.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -3973,7 +4234,7 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
// continue using texture, fill it with new data
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiResourceUpdateBatch, QRhi, QRhiTextureRenderTarget
@@ -4140,9 +4401,10 @@ bool QRhiRenderBuffer::createFrom(NativeRenderBuffer src)
\brief 64-bit integer containing the native object handle.
With OpenGL, the native handle is a GLuint value, so \c object can then be
- cast to a GLuint. With Vulkan, the native handle is a VkImage, so \c
- object can be cast to a VkImage. With Direct3D 11 and Metal \c
- object contains a ID3D11Texture2D or MTLTexture pointer, respectively.
+ cast to a GLuint. With Vulkan, the native handle is a VkImage, so \c object
+ can be cast to a VkImage. With Direct3D 11 and Metal \c object contains a
+ ID3D11Texture2D or MTLTexture pointer, respectively. With Direct3D 12
+ \c object contains a ID3D12Resource pointer.
*/
/*!
@@ -4359,12 +4621,93 @@ void QRhiTexture::setNativeLayout(int layout)
*/
/*!
+ \struct QRhiTexture::ViewFormat
+ \inmodule QtGui
+ \since 6.8
+ \brief Specifies the view format for reading or writing from or to the texture.
+
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+ for details.
+ */
+
+/*!
+ \variable QRhiTexture::ViewFormat::format
+ */
+
+/*!
+ \variable QRhiTexture::ViewFormat::srgb
+ */
+
+/*!
+ \fn QRhiTexture::ViewFormat QRhiTexture::readViewFormat() const
+ \since 6.8
+ \return the view format used when sampling the texture. When not called, the view
+ format is assumed to be the same as format().
+ */
+
+/*!
+ \fn void QRhiTexture::setReadViewFormat(const ViewFormat &fmt)
+ \since 6.8
+
+ Sets the shader resource view format (or the format of the view used for
+ sampling the texture) to \a fmt. By default the same format (and sRGB-ness)
+ is used as the texture itself, and in most cases this function does not need
+ to be called.
+
+ This setting is only taken into account when the \l TextureViewFormat
+ feature is reported as supported.
+
+ \note This functionality is provided to allow "casting" between
+ non-sRGB and sRGB in order to get the shader reads perform, or not perform,
+ the implicit sRGB conversions. Other types of casting may or may not be
+ functional.
+ */
+
+/*!
+ \fn QRhiTexture::ViewFormat QRhiTexture::writeViewFormat() const
+ \since 6.8
+ \return the view format used when writing to the texture and when using it
+ with image load/store. When not called, the view format is assumed to be the
+ same as format().
+ */
+
+/*!
+ \fn void QRhiTexture::setWriteViewFormat(const ViewFormat &fmt)
+ \since 6.8
+
+ Sets the render target view format to \a fmt. By default the same format
+ (and sRGB-ness) is used as the texture itself, and in most cases this
+ function does not need to be called.
+
+ One common use case for providing a write view format is working with
+ externally provided textures that, outside of our control, use an sRGB
+ format with 3D APIs such as Vulkan or Direct 3D, but the rendering engine is
+ already prepared to handle linearization and conversion to sRGB at the end
+ of its shading pipeline. In this case what is wanted when rendering into
+ such a texture is a render target view (e.g. VkImageView) that has the same,
+ but non-sRGB format. (if e.g. from an OpenXR implementation one gets a
+ VK_FORMAT_R8G8B8A8_SRGB texture, it is likely that rendering into it should
+ be done using a VK_FORMAT_R8G8B8A8_UNORM view, if that is what the rendering
+ engine's pipeline requires; in this example one would call this function
+ with a ViewFormat that has a format of QRhiTexture::RGBA8 and \c srgb set to
+ \c false).
+
+ This setting is only taken into account when the \l TextureViewFormat
+ feature is reported as supported.
+
+ \note This functionality is provided to allow "casting" between
+ non-sRGB and sRGB in order to get the shader write not perform, or perform,
+ the implicit sRGB conversions. Other types of casting may or may not be
+ functional.
+ */
+
+/*!
\class QRhiSampler
\inmodule QtGui
\since 6.6
\brief Sampler resource.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -4505,7 +4848,7 @@ QRhiResource::Type QRhiSampler::resourceType() const
a collection of attachments (color, depth, stencil) and describes how those
attachments are used.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -4624,7 +4967,7 @@ const QRhiNativeHandles *QRhiRenderPassDescriptor::nativeHandles()
QRhiSwapChain returns when calling
\l{QRhiSwapChain::currentFrameRenderTarget()}{currentFrameRenderTarget()}.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiSwapChainRenderTarget, QRhiTextureRenderTarget
@@ -4703,7 +5046,7 @@ QRhiSwapChainRenderTarget::QRhiSwapChainRenderTarget(QRhiImplementation *rhi, QR
QRhiSwapChainRenderTarget. This is what
QRhiSwapChain::currentFrameRenderTarget() returns.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiSwapChain
@@ -4752,7 +5095,7 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const
// rt can now be used with beginPass()
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -4768,7 +5111,19 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const
\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.
+ GPUs, but allows preserving the existing contents between passes. When doing
+ multisample rendering with a resolve texture set, setting this flag also
+ requests the multisample color data to be stored (written out) to the
+ multisample texture or render buffer. (for non-multisample rendering the
+ color data is always stored, but for MSAA storing the multisample data
+ decreases efficiency for certain GPU architectures, hence defaulting to not
+ writing it out) Note however that this is non-portable: in some cases there
+ is no intermediate multisample texture on the graphics API level, e.g. when
+ using OpenGL ES's \c{GL_EXT_multisampled_render_to_texture} as it is all
+ implicit, handled by the OpenGL ES implementation. In that case,
+ PreserveColorContents will likely have no effect. Therefore, avoid relying
+ on this flag when using multisample rendering and the color attachment is
+ using a multisample QRhiTexture (not QRhiRenderBuffer).
\value PreserveDepthStencilContents Indicates that the contents of the
depth texture is to be loaded when starting a render pass, instead
@@ -4776,6 +5131,13 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const
(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.
+
+ \value DoNotStoreDepthStencilContents Indicates that the contents of the
+ depth texture does not need to be written out. Relevant only when a
+ QRhiTexture, not QRhiRenderBuffer, is used as the depth-stencil buffer,
+ because for QRhiRenderBuffer this is implicit. When a depthResolveTexture is
+ set, the flag is not relevant, because the behavior is then as if the flag
+ was set. This enum value is introduced in Qt 6.8.
*/
/*!
@@ -4933,11 +5295,19 @@ QRhiResource::Type QRhiTextureRenderTarget::resourceType() const
cb->setShaderResources(srb2); // binds srb2
\endcode
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
/*!
+ \typedef QRhiShaderResourceBindingSet
+ \relates QRhi
+ \since 6.7
+
+ Synonym for QRhiShaderResourceBindings.
+*/
+
+/*!
\internal
*/
QRhiShaderResourceBindings::QRhiShaderResourceBindings(QRhiImplementation *rhi)
@@ -5064,7 +5434,7 @@ void QRhiImplementation::updateLayoutDesc(QRhiShaderResourceBindings *srb)
static functions such as uniformBuffer() or sampledTexture() to get an
instance.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -6085,7 +6455,7 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
four channels, depth test/write are disabled, stencil operations are
disabled.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiCommandBuffer, QRhi
@@ -6258,7 +6628,7 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
mode Qt Quick uses, it is enough to set the \c enable flag to true while
leaving other values at their defaults.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -6308,7 +6678,7 @@ QDebug operator<<(QDebug dbg, const QRhiShaderResourceBindings &srb)
\li compareOp \l Always
\endlist
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -6688,6 +7058,29 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
*/
/*!
+ \fn int QRhiGraphicsPipeline::multiViewCount() const
+ \return the view count. The default is 0, indicating no multiview rendering.
+ \since 6.7
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setMultiViewCount(int count)
+ Sets the view \a count for multiview rendering. The default is 0,
+ indicating no multiview rendering.
+ \a count must be 2 or larger to trigger multiview rendering.
+
+ Multiview is only available when the \l{QRhi::MultiView}{MultiView feature}
+ is reported as supported. The render target must be a 2D texture array, and
+ the color attachment for the render target must have the same \a count set.
+
+ See QRhiColorAttachment::setMultiViewCount() for further details on
+ multiview rendering.
+
+ \since 6.7
+ \sa QRhi::MultiView, QRhiColorAttachment::setMultiViewCount()
+ */
+
+/*!
\class QRhiSwapChain
\inmodule QtGui
\since 6.6
@@ -6825,7 +7218,7 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
appropriate sample count also via QSurfaceFormat, by calling
QSurfaceFormat::setDefaultFormat() before initializing the QRhi.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -6885,6 +7278,14 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
\enum QRhiSwapChain::Format
Describes the swapchain format. The default format is SDR.
+ This enum is used with
+ \l{QRhiSwapChain::isFormatSupported()}{isFormatSupported()} to check
+ upfront if creating the swapchain with the given format is supported by the
+ platform and the window's associated screen, and with
+ \l{QRhiSwapChain::setFormat()}{setFormat()}
+ to set the requested format in the swapchain before calling
+ \l{QRhiSwapChain::createOrResize()}{createOrResize()} for the first time.
+
\value SDR 8-bit RGBA or BGRA, depending on the backend and platform. With
OpenGL ES in particular, it could happen that the platform provides less
than 8 bits (e.g. due to EGL and the QSurfaceFormat choosing a 565 or 444
@@ -6896,10 +7297,14 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
(same as SDR/sRGB) and linear colors. Conversion to the display's native
color space (such as, HDR10) is performed by the windowing system. On
Windows this is the canonical color space of the system compositor, and is
- the recommended format for HDR swapchains in general.
+ the recommended format for HDR swapchains in general on desktop platforms.
\value HDR10 10-bit unsigned int RGB or BGR with 2 bit alpha, high dynamic
range, HDR10 (Rec. 2020) color space with an ST2084 PQ transfer function.
+
+ \value HDRExtendedDisplayP3Linear 16-bit float RGBA, high dynamic range,
+ extended linear Display P3 color space. The primary choice for HDR on
+ platforms such as iOS and VisionOS.
*/
/*!
@@ -6993,6 +7398,21 @@ QRhiResource::Type QRhiSwapChain::resourceType() const
time. If the result is true for a HDR format, then creating the swapchain
with that format is expected to succeed as long as the window is not moved
to another screen in the meantime.
+
+ The main use of this function is to call it before the first
+ createOrResize() after the window is already set. This allow the QRhi
+ backends to perform platform or windowing system specific queries to
+ determine if the window (and the screen it is on) is capable of true HDR
+ output with the specified format.
+
+ When the format is reported as supported, call setFormat() to set the
+ requested format and call createOrResize(). Be aware of the consequences
+ however: successfully requesting a HDR format will involve having to deal
+ with a different color space, possibly doing white level correction for
+ non-HDR-aware content, adjusting tonemapping methods, adjusting offscreen
+ render target settings, etc.
+
+ \sa setFormat()
*/
/*!
@@ -7041,10 +7461,9 @@ QRhiResource::Type QRhiSwapChain::resourceType() const
is backed by two color buffers, one for each eye, instead of just one.
When stereoscopic rendering is not supported, the return value will be
- the default target. For the time being the only backend and 3D API where traditional
- stereoscopic rendering is supported is OpenGL (excluding OpenGL ES), in
+ the default target. It is supported by all hardware backends except for Metal, in
combination with \l QSurfaceFormat::StereoBuffers, assuming it is supported
- by the graphics and display driver stack at run time. All other backends
+ by the graphics and display driver stack at run time. Metal and Null backends
are going to return the default render target from this overload.
\note the value must not be cached and reused between frames
@@ -7111,6 +7530,15 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
/*!
\fn void QRhiSwapChain::setFormat(Format f)
Sets the format \a f.
+
+ Avoid setting formats that are reported as unsupported from
+ isFormatSupported(). Note that support for a given format may depend on the
+ screen the swapchain's associated window is opened on. On some platforms,
+ such as Windows and macOS, for HDR output to work it is necessary to have
+ HDR output enabled in the display settings.
+
+ See isFormatSupported(), \l QRhiSwapChainHdrInfo, and \l Format for more
+ information on high dynamic range output.
*/
/*!
@@ -7172,11 +7600,12 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
\brief Describes the high dynamic range related information of the
swapchain's associated output.
- To perform tonemapping, one often needs to know the maximum luminance of
- the display the swapchain's window is associated with. While this is often
- made user-configurable, it can be highly useful to set defaults based on
- the values reported by the display itself, thus providing a decent starting
- point.
+ To perform HDR-compatible tonemapping, where the target range is not [0,1],
+ one often needs to know the maximum luminance of the display the
+ swapchain's window is associated with. While this is often made
+ user-configurable (think brightness, gamma and similar settings in games),
+ it can be highly useful to set defaults based on the values reported by the
+ display itself, thus providing a decent starting point.
There are some problems however: the information is exposed in different
forms on different platforms, whereas with cross-platform graphics APIs
@@ -7184,11 +7613,6 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
information is not in the scope of the API (and may rather be retrievable
via other platform-specific means, if any).
- The struct returned from QRhiSwapChain::hdrInfo() contains either some
- hard-coded defaults, indicated by the \c isHardCodedDefaults field, or real
- values received from an API such as DXGI (IDXGIOutput6) or Cocoa
- (NSScreen). The default is 1000 nits for maximum luminance.
-
With Metal on macOS/iOS, there is no luminance values exposed in the
platform APIs. Instead, the maximum color component value, that would be
1.0 in a non-HDR setup, is provided. The \c limitsType field indicates what
@@ -7197,10 +7621,23 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
fit.
With an API like Vulkan, where there is no way to get such information, the
- values are always the built-in defaults and \c isHardCodedDefaults is
- always true.
-
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ values are always the built-in defaults.
+
+ Therefore, the struct returned from QRhiSwapChain::hdrInfo() contains
+ either some hard-coded defaults or real values received from an API such as
+ DXGI (IDXGIOutput6) or Cocoa (NSScreen). When no platform queries are
+ available (or needs using platform facilities out of scope for QRhi), the
+ hard-coded defaults are a maximum luminance of 1000 nits and an SDR white
+ level of 200.
+
+ The struct also exposes the presumed luminance behavior of the platform and
+ its compositor, to indicate what a color component value of 1.0 is treated
+ as in a HDR color buffer. In some cases it will be necessary to perform
+ color correction of non-HDR content composited with HDR content. To enable
+ this, the SDR white level is queried from the system on some platforms
+ (Windows) and exposed here.
+
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhiSwapChain::hdrInfo()
@@ -7217,16 +7654,20 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
*/
/*!
- \variable QRhiSwapChainHdrInfo::isHardCodedDefaults
+ \enum QRhiSwapChainHdrInfo::LuminanceBehavior
- Set to true when the data in the QRhiSwapChainHdrInfo consists entirely of
- the hard-coded default values, for example because there is no way to query
- the relevant information with a given graphics API or platform. (or because
- querying it can be achieved only by means, e.g. platform APIs in some other
- area, that are out of scope for the QRhi layer of the Qt graphics stack to
- handle)
+ \value SceneReferred Indicates that the color value of 1.0 is interpreted
+ as 80 nits. This is the behavior of HDR-enabled windows with the Windows
+ compositor. See
+ \l{https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range}{this
+ page} for more information on HDR on Windows.
- \sa QRhiSwapChain::hdrInfo()
+ \value DisplayReferred Indicates that the color value of 1.0 is interpreted
+ as the value of the SDR white. (which can be e.g. 200 nits, but will vary
+ depending on screen brightness) This is the behavior of HDR-enabled windows
+ on Apple platforms. See
+ \l{https://developer.apple.com/documentation/metal/hdr_content/displaying_hdr_content_in_a_metal_layer}{this
+ page} for more information on Apple's EDR system.
*/
/*!
@@ -7256,23 +7697,92 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
} luminanceInNits;
\endcode
- Whereas for macOS/iOS, the maximum color component value (e.g. supposedly
- something larger than 1.0f) is provided:
+ On Windows the minimum and maximum luminance depends on the screen
+ brightness. While not relevant for desktops, on laptops the screen
+ brightness may change at any time. Increasing brightness implies decreased
+ maximum luminance. In addition, the results may also be dependent on the
+ HDR Content Brightness set in Windows Settings' System/Display/HDR view,
+ if there is such a setting.
+
+ Note however that the changes made to the laptop screen's brightness or in
+ the system settings while the application is running are not necessarily
+ reflected in the returned values, meaning calling hdrInfo() again may still
+ return the same luminance range as before for the rest of the process'
+ lifetime. The exact behavior is up to DXGI and Qt has no control over it.
+
+ \note The Windows compositor works in scene-referred mode for HDR content.
+ A color component value of 1.0 corresponds to a luminance of 80 nits. When
+ rendering non-HDR content (e.g. 2D UI elements), the correction of the
+ white level is often necessary. (e.g., outputting the fragment color (1, 1,
+ 1) will likely lead to showing a shade of white that is too dim on-screen)
+ See \l sdrWhiteLevel.
+
+ For macOS/iOS, the current maximum and potential maximum color
+ component values are provided:
\code
struct {
float maxColorComponentValue;
+ float maxPotentialColorComponentValue;
} colorComponentValue;
\endcode
+ The value may depend on the screen brightness, which on laptops means that
+ the result may change in the next call to hdrInfo() if the brightness was
+ changed in the meantime. The maximum screen brightness implies a maximum
+ color value of 1.0.
+
+ \note Apple's EDR is display-referred. 1.0 corresponds to a luminance level
+ of SDR white (e.g. 200 nits), the value of which varies based on the screen
+ brightness and possibly other settings. The exact luminance value for that,
+ or the maximum luminance of the display, are not exposed to the
+ applications.
+
+ \note It has been observed that the color component values are not set to
+ the correct larger-than-1 value right away on startup on some macOS
+ systems, but the values tend to change during or after the first frame.
+
\sa QRhiSwapChain::hdrInfo()
*/
/*!
+ \variable QRhiSwapChainHdrInfo::luminanceBehavior
+
+ Describes the platform's presumed behavior with regards to color values.
+
+ \sa sdrWhiteLevel
+ */
+
+/*!
+ \variable QRhiSwapChainHdrInfo::sdrWhiteLevel
+
+ On Windows this is the dynamic SDR white level in nits. The value is
+ dependent on the screen brightness (on laptops), and the SDR or HDR Content
+ Brightness settings in the Windows settings' System/Display/HDR view.
+
+ To perform white level correction for non-HDR (SDR) content, such as 2D UI
+ elemenents, multiply the final color with sdrWhiteLevel / 80.0 whenever
+ \l luminanceBehavior is SceneReferred. (assuming Windows and a linear
+ extended sRGB (scRGB) color space)
+
+ On other platforms the value is always a pre-defined value, 200. This may
+ not match the system's actual SDR white level, but the value of this
+ variable is not relevant in practice when the \l luminanceBehavior is
+ DisplayReferred, because then the color component value of 1.0 refers to
+ the SDR white by default.
+
+ \sa luminanceBehavior
+*/
+
+/*!
\return the HDR information for the associated display.
- The returned struct is always the default one if createOrResize() has not
- been successfully called yet.
+ Do not assume that this is a cheap operation. Depending on the platform,
+ this function makes various platform queries which may have a performance
+ impact.
+
+ \note Can be called before createOrResize() as long as the window is
+ \l{setWindow()}{set}.
\note What happens when moving a window with an initialized swapchain
between displays (HDR to HDR with different characteristics, HDR to SDR,
@@ -7287,10 +7797,11 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
QRhiSwapChainHdrInfo QRhiSwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info;
- info.isHardCodedDefaults = true;
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = 0.0f;
info.limits.luminanceInNits.maxLuminance = 1000.0f;
+ info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred;
+ info.sdrWhiteLevel = 200.0f;
return info;
}
@@ -7298,7 +7809,7 @@ QRhiSwapChainHdrInfo QRhiSwapChain::hdrInfo()
QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info)
{
QDebugStateSaver saver(dbg);
- dbg.nospace() << "QRhiSwapChainHdrInfo(" << (info.isHardCodedDefaults ? "with hard-coded defaults" : "queried from system");
+ dbg.nospace() << "QRhiSwapChainHdrInfo(";
switch (info.limitsType) {
case QRhiSwapChainHdrInfo::LuminanceInNits:
dbg.nospace() << " minLuminance=" << info.limits.luminanceInNits.minLuminance
@@ -7306,6 +7817,15 @@ QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info)
break;
case QRhiSwapChainHdrInfo::ColorComponentValue:
dbg.nospace() << " maxColorComponentValue=" << info.limits.colorComponentValue.maxColorComponentValue;
+ dbg.nospace() << " maxPotentialColorComponentValue=" << info.limits.colorComponentValue.maxPotentialColorComponentValue;
+ break;
+ }
+ switch (info.luminanceBehavior) {
+ case QRhiSwapChainHdrInfo::SceneReferred:
+ dbg.nospace() << " scene-referred, SDR white level=" << info.sdrWhiteLevel;
+ break;
+ case QRhiSwapChainHdrInfo::DisplayReferred:
+ dbg.nospace() << " display-referred";
break;
}
dbg.nospace() << ')';
@@ -7325,7 +7845,7 @@ QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info)
\note Setting the shader is mandatory.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -7407,7 +7927,7 @@ QRhiComputePipeline::QRhiComputePipeline(QRhiImplementation *rhi)
QRhiSwapChain::currentFrameCommandBuffer(), or, in case of rendering
completely offscreen, initializing one via QRhi::beginOffscreenFrame().
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -7733,6 +8253,17 @@ void QRhiImplementation::textureFormatInfo(QRhiTexture::Format format, const QSi
*bytesPerPixel = bpc;
}
+bool QRhiImplementation::isStencilSupportingFormat(QRhiTexture::Format format) const
+{
+ switch (format) {
+ case QRhiTexture::D24S8:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
bool QRhiImplementation::sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps)
{
if (ps->cbeginShaderStages() == ps->cendShaderStages()) {
@@ -7852,6 +8383,41 @@ bool QRhiImplementation::sanityCheckShaderResourceBindings(QRhiShaderResourceBin
return true;
}
+int QRhiImplementation::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);
+ const QList<int> supported = supportedSampleCounts();
+ int result = 1;
+
+ // Stay compatible with Qt 5 in that requesting an unsupported sample count
+ // is not an error (although we still do a categorized debug print about
+ // this), and rather a supported value, preferably a close one, not just 1,
+ // is used instead. This is actually deviating from Qt 5 as that performs a
+ // clamping only and does not handle cases such as when sample count 2 is
+ // not supported but 4 is. (OpenGL handles things like that gracefully,
+ // other APIs may not, so improve this by picking the next largest, or in
+ // absence of that, the largest value; this with the goal to not reduce
+ // quality by rather picking a larger-than-requested value than a smaller one)
+
+ for (int i = 0, ie = supported.count(); i != ie; ++i) {
+ // assumes the 'supported' list is sorted
+ if (supported[i] >= s) {
+ result = supported[i];
+ break;
+ }
+ }
+
+ if (result != s) {
+ if (result == 1 && !supported.isEmpty())
+ result = supported.last();
+ qCDebug(QRHI_LOG_INFO, "Attempted to set unsupported sample count %d, using %d instead",
+ sampleCount, result);
+ }
+
+ return result;
+}
+
/*!
\internal
*/
@@ -7867,11 +8433,11 @@ QRhi::~QRhi()
if (!d)
return;
+ runCleanup();
+
qDeleteAll(d->pendingDeleteResources);
d->pendingDeleteResources.clear();
- runCleanup();
-
d->destroy();
delete d;
}
@@ -7959,7 +8525,7 @@ QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRh
break;
#endif
case Metal:
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
r->d = new QRhiMetal(static_cast<QRhiMetalInitParams *>(params),
static_cast<QRhiMetalNativeHandles *>(importDevice));
break;
@@ -7969,10 +8535,18 @@ QRhi *QRhi::create(Implementation impl, QRhiInitParams *params, Flags flags, QRh
#endif
case D3D12:
#ifdef Q_OS_WIN
+#ifdef QRHI_D3D12_AVAILABLE
r->d = new QRhiD3D12(static_cast<QRhiD3D12InitParams *>(params),
static_cast<QRhiD3D12NativeHandles *>(importDevice));
break;
#else
+ qWarning("Qt was built without Direct3D 12 support. "
+ "This is likely due to having ancient SDK headers (such as d3d12.h) in the Qt build environment. "
+ "Rebuild Qt with an SDK supporting D3D12 features introduced in Windows 10 version 1703, "
+ "or use an MSVC build as those typically are built with more up-to-date SDKs.");
+ break;
+#endif
+#else
qWarning("This platform has no Direct3D 12 support");
break;
#endif
@@ -8010,7 +8584,7 @@ bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params)
// create() and then drop the result.
if (impl == Metal) {
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
ok = QRhiMetal::probe(static_cast<QRhiMetalInitParams *>(params));
#endif
} else {
@@ -8028,7 +8602,7 @@ bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params)
\brief Opaque data describing native objects needed to set up a swapchain.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
\sa QRhi::updateSwapChainProxyData()
@@ -8061,7 +8635,7 @@ bool QRhi::probe(QRhi::Implementation impl, QRhiInitParams *params)
*/
QRhiSwapChainProxyData QRhi::updateSwapChainProxyData(QRhi::Implementation impl, QWindow *window)
{
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
+#if QT_CONFIG(metal)
if (impl == Metal)
return QRhiMetal::updateSwapChainProxyData(window);
#else
@@ -8113,9 +8687,11 @@ const char *QRhi::backendName() const
/*!
\enum QRhiDriverInfo::DeviceType
- Specifies the graphics device's type, when the information is available. In
- practice this is only applicable with Vulkan and Metal. With others the
- value will always be UnknownDevice.
+ Specifies the graphics device's type, when the information is available.
+
+ In practice this is only applicable with Vulkan and Metal. With Direct 3D
+ 11 and 12, using an adapter with the software flag set leads to the value
+ \c CpuDevice. Otherwise, and with OpenGL, the value is always UnknownDevice.
\value UnknownDevice
\value IntegratedDevice
@@ -8141,7 +8717,7 @@ const char *QRhi::backendName() const
for OpenGL and Metal. deviceType is always UnknownDevice for OpenGL and
Direct 3D.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -8236,6 +8812,33 @@ void QRhi::addCleanupCallback(const CleanupCallback &callback)
}
/*!
+ \overload
+
+ Registers \a callback to be invoked either when the QRhi is destroyed or
+ when runCleanup() is called. This overload takes an opaque pointer, \a key,
+ that is used to ensure that a given callback is registered (and so called)
+ only once.
+
+ \sa removeCleanupCallback()
+ */
+void QRhi::addCleanupCallback(const void *key, const CleanupCallback &callback)
+{
+ d->addCleanupCallback(key, callback);
+}
+
+/*!
+ Deregisters the callback with \a key. If no cleanup callback was registered
+ with \a key, the function does nothing. Callbacks registered without a key
+ cannot be removed.
+
+ \sa addCleanupCallback()
+ */
+void QRhi::removeCleanupCallback(const void *key)
+{
+ d->removeCleanupCallback(key);
+}
+
+/*!
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
@@ -8249,6 +8852,11 @@ void QRhi::runCleanup()
f(this);
d->cleanupCallbacks.clear();
+
+ for (auto it = d->keyedCleanupCallbacks.cbegin(), end = d->keyedCleanupCallbacks.cend(); it != end; ++it)
+ it.value()(this);
+
+ d->keyedCleanupCallbacks.clear();
}
/*!
@@ -8271,7 +8879,7 @@ void QRhi::runCleanup()
To get an available, empty batch from the pool, call
QRhi::nextResourceUpdateBatch().
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -8412,6 +9020,8 @@ void QRhiResourceUpdateBatch::uploadStaticBuffer(QRhiBuffer *buf, quint32 offset
}
/*!
+ \overload
+
Enqueues updating the entire QRhiBuffer \a buf created with the type
QRhiBuffer::Immutable or QRhiBuffer::Static.
*/
@@ -9267,9 +9877,9 @@ const QRhiNativeHandles *QRhiCommandBuffer::nativeHandles()
called when the pass recording was started with specifying
QRhiCommandBuffer::ExternalContent.
- 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
+ With Vulkan, Metal, or Direct3D 12 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
@@ -9317,8 +9927,17 @@ void QRhiCommandBuffer::endExternal()
}
/*!
- \return the last available timestamp, in seconds. The value indicates the
- elapsed time on the GPU during the last completed frame.
+ \return the last available timestamp, in seconds, when
+ \l QRhi::EnableTimestamps was enabled when creating the QRhi. The value
+ indicates the elapsed time on the GPU during the last completed frame.
+
+ \note Do not expect results other than 0 when the QRhi::Timestamps feature
+ is not reported as supported, or when QRhi::EnableTimestamps was not passed
+ to QRhi::create(). There are exceptions to this, because with some graphics
+ APIs (Metal) timings are available without having to perform extra
+ operations (timestamp queries), but portable applications should always
+ consciously opt-in to timestamp collection when they know it is needed, and
+ call this function accordingly.
Care must be exercised with the interpretation of the value, as its
precision and granularity is often not controlled by Qt, and depends on the
@@ -9326,25 +9945,47 @@ void QRhiCommandBuffer::endExternal()
the values between different graphics APIs and hardware is discouraged and
may be meaningless.
- The timing values may become available asynchronously. The returned value
- may therefore be 0 or the last known value referring to some previous
- frame. The value my also become 0 again under certain conditions, such as
- when resizing the window. It can be expected that the most up-to-date
- available value is retrieved in beginFrame() and becomes queriable via this
- function once beginFrame() returns.
+ When the frame was recorded with \l{QRhi::beginFrame()}{beginFrame()} and
+ \l{QRhi::endFrame()}{endFrame()}, i.e., with a swapchain, the timing values
+ will likely become available asynchronously. The returned value may
+ therefore be 0 (e.g., for the first 1-2 frames) or the last known value
+ referring to some previous frame. The value my also
+ become 0 again under certain conditions, such as when resizing the window.
+ It can be expected that the most up-to-date available value is retrieved in
+ beginFrame() and becomes queriable via this function once beginFrame()
+ returns.
\note Do not assume that the value refers to the previous
(\c{currently_recorded - 1}) frame. It may refer to \c{currently_recorded -
2} or \c{currently_recorded - 3} as well. The exact behavior may depend on
the graphics API and its implementation.
- \note The result is always 0 when the QRhi::Timestamps feature is not
- reported as supported, or when QRhi::EnableTimestamps was not passed to
- QRhi::create(). There are exceptions to the latter, because with some
- graphics APIs timings are available without having to perform extra
- operations, but portable applications should always consciously opt-in to
- timestamp collection when they know it is needed, and call this function
- accordingly.
+ On the other hand, with offscreen frames the returned value is up-to-date
+ once \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} returns, because
+ offscreen frames reduce GPU pipelining and wait the the commands to be
+ complete.
+
+ \note This means that, unlike with swapchain frames, with offscreen frames
+ the returned value is guaranteed to refer to the frame that has just been
+ submitted and completed. (assuming this function is called after
+ endOffscreenFrame() but before the next beginOffscreenFrame())
+
+ Watch out for the consequences of GPU frequency scaling and GPU clock
+ changes, depending on the platform. For example, on Windows the returned
+ timing may vary in a quite wide range between frames with modern graphics
+ cards, even when submitting frames with a similar, or the same workload.
+ This is out of scope for Qt to control and solve, generally speaking.
+ However, the D3D12 backend automatically calls
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-setstablepowerstate}{ID3D12Device::SetStablePowerState()}
+ whenever the environment variable \c QT_D3D_STABLE_POWER_STATE is set to a
+ non-zero value. This can greatly stabilize the result. It can also have a
+ non-insignificant effect on the CPU-side timings measured via QElapsedTimer
+ for example, especially when offscreen frames are involved.
+
+ \note Do not and never ship applications to production with
+ \c QT_D3D_STABLE_POWER_STATE set. See the Windows API documentation for details.
+
+ \sa QRhi::Timestamps, QRhi::EnableTimestamps
*/
double QRhiCommandBuffer::lastCompletedGpuTime()
{
@@ -9693,7 +10334,7 @@ void QRhi::setPipelineCacheData(const QByteArray &data)
\brief Statistics provided from the underlying memory allocator.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -10011,6 +10652,14 @@ QRhiTexture *QRhi::newTextureArray(QRhiTexture::Format format,
minification filter \a minFilter, mipmapping mode \a mipmapMode, and the
addressing (wrap) modes \a addressU, \a addressV, and \a addressW.
+ \note Setting \a mipmapMode to a value other than \c None implies that
+ images for all relevant mip levels will be provided either via
+ \l{QRhiResourceUpdateBatch::uploadTexture()}{texture uploads} or by calling
+ \l{QRhiResourceUpdateBatch::generateMips()}{generateMips()} on the texture
+ that is used with this sampler. Attempting to use the sampler with a
+ texture that has no data for all relevant mip levels will lead to rendering
+ errors, with the exact behavior dependent on the underlying graphics API.
+
\sa QRhiResource::destroy()
*/
QRhiSampler *QRhi::newSampler(QRhiSampler::Filter magFilter,
diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h
index 8ec6630acf..d20b7e00d1 100644
--- a/src/gui/rhi/qrhi.h
+++ b/src/gui/rhi/qrhi.h
@@ -250,7 +250,15 @@ public:
Half4,
Half3,
Half2,
- Half
+ Half,
+ UShort4,
+ UShort3,
+ UShort2,
+ UShort,
+ SShort4,
+ SShort3,
+ SShort2,
+ SShort,
};
QRhiVertexInputAttribute() = default;
@@ -495,7 +503,7 @@ public:
quint32 maybeSize;
bool hasDynamicOffset;
};
- static const int MAX_TEX_SAMPLER_ARRAY_SIZE = 16;
+ static constexpr int MAX_TEX_SAMPLER_ARRAY_SIZE = 16;
struct TextureAndOrSamplerData {
int count;
TextureAndSampler texSamplers[MAX_TEX_SAMPLER_ARRAY_SIZE];
@@ -535,7 +543,7 @@ public:
}
};
- static const int LAYOUT_DESC_ENTRIES_PER_BINDING = 4;
+ static constexpr int LAYOUT_DESC_ENTRIES_PER_BINDING = 4;
template<typename Output>
static void serializeLayoutDescription(const QRhiShaderResourceBinding *first,
@@ -592,6 +600,9 @@ public:
int resolveLevel() const { return m_resolveLevel; }
void setResolveLevel(int level) { m_resolveLevel = level; }
+ int multiViewCount() const { return m_multiViewCount; }
+ void setMultiViewCount(int count) { m_multiViewCount = count; }
+
private:
QRhiTexture *m_texture = nullptr;
QRhiRenderBuffer *m_renderBuffer = nullptr;
@@ -600,6 +611,7 @@ private:
QRhiTexture *m_resolveTexture = nullptr;
int m_resolveLayer = 0;
int m_resolveLevel = 0;
+ int m_multiViewCount = 0;
};
Q_DECLARE_TYPEINFO(QRhiColorAttachment, Q_RELOCATABLE_TYPE);
@@ -630,10 +642,14 @@ public:
QRhiTexture *depthTexture() const { return m_depthTexture; }
void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; }
+ QRhiTexture *depthResolveTexture() const { return m_depthResolveTexture; }
+ void setDepthResolveTexture(QRhiTexture *tex) { m_depthResolveTexture = tex; }
+
private:
QVarLengthArray<QRhiColorAttachment, 8> m_colorAttachments;
QRhiRenderBuffer *m_depthStencilBuffer = nullptr;
QRhiTexture *m_depthTexture = nullptr;
+ QRhiTexture *m_depthResolveTexture = nullptr;
};
class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription
@@ -979,6 +995,15 @@ public:
int sampleCount() const { return m_sampleCount; }
void setSampleCount(int s) { m_sampleCount = s; }
+ struct ViewFormat {
+ QRhiTexture::Format format;
+ bool srgb;
+ };
+ ViewFormat readViewFormat() const { return m_readViewFormat; }
+ void setReadViewFormat(const ViewFormat &fmt) { m_readViewFormat = fmt; }
+ ViewFormat writeViewFormat() const { return m_writeViewFormat; }
+ void setWriteViewFormat(const ViewFormat &fmt) { m_writeViewFormat = fmt; }
+
virtual bool create() = 0;
virtual NativeTexture nativeTexture();
virtual bool createFrom(NativeTexture src);
@@ -995,6 +1020,8 @@ protected:
Flags m_flags;
int m_arrayRangeStart = -1;
int m_arrayRangeLength = -1;
+ ViewFormat m_readViewFormat = { UnknownFormat, false };
+ ViewFormat m_writeViewFormat = { UnknownFormat, false };
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiTexture::Flags)
@@ -1158,7 +1185,8 @@ class Q_GUI_EXPORT QRhiTextureRenderTarget : public QRhiRenderTarget
public:
enum Flag {
PreserveColorContents = 1 << 0,
- PreserveDepthStencilContents = 1 << 1
+ PreserveDepthStencilContents = 1 << 1,
+ DoNotStoreDepthStencilContents = 1 << 2
};
Q_DECLARE_FLAGS(Flags, Flag)
@@ -1213,7 +1241,7 @@ public:
virtual void updateResources(UpdateFlags flags = {}) = 0;
protected:
- static const int BINDING_PREALLOC = 12;
+ static constexpr int BINDING_PREALLOC = 12;
QRhiShaderResourceBindings(QRhiImplementation *rhi);
QVarLengthArray<QRhiShaderResourceBinding, BINDING_PREALLOC> m_bindings;
size_t m_layoutDescHash = 0;
@@ -1233,6 +1261,11 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QRhiShaderResourceBindings::UpdateFlags)
Q_GUI_EXPORT QDebug operator<<(QDebug, const QRhiShaderResourceBindings &);
#endif
+// The proper name. Until it gets rolled out universally, have the better name
+// as a typedef. Eventually it should be reversed (the old name being a typedef
+// to the new one).
+using QRhiShaderResourceBindingSet = QRhiShaderResourceBindings;
+
class Q_GUI_EXPORT QRhiGraphicsPipeline : public QRhiResource
{
public:
@@ -1437,6 +1470,9 @@ public:
PolygonMode polygonMode() const {return m_polygonMode; }
void setPolygonMode(PolygonMode mode) {m_polygonMode = mode; }
+ int multiViewCount() const { return m_multiViewCount; }
+ void setMultiViewCount(int count) { m_multiViewCount = count; }
+
virtual bool create() = 0;
protected:
@@ -1460,6 +1496,7 @@ protected:
float m_slopeScaledDepthBias = 0.0f;
int m_patchControlPointCount = 3;
PolygonMode m_polygonMode = Fill;
+ int m_multiViewCount = 0;
QVarLengthArray<QRhiShaderStage, 4> m_shaderStages;
QRhiVertexInputLayout m_vertexInputLayout;
QRhiShaderResourceBindings *m_shaderResourceBindings = nullptr;
@@ -1472,11 +1509,16 @@ Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_RELOCATABLE_TYPE);
struct QRhiSwapChainHdrInfo
{
- bool isHardCodedDefaults;
enum LimitsType {
LuminanceInNits,
ColorComponentValue
};
+
+ enum LuminanceBehavior {
+ SceneReferred,
+ DisplayReferred
+ };
+
LimitsType limitsType;
union {
struct {
@@ -1485,8 +1527,11 @@ struct QRhiSwapChainHdrInfo
} luminanceInNits;
struct {
float maxColorComponentValue;
+ float maxPotentialColorComponentValue;
} colorComponentValue;
} limits;
+ LuminanceBehavior luminanceBehavior;
+ float sdrWhiteLevel;
};
Q_DECLARE_TYPEINFO(QRhiSwapChainHdrInfo, Q_RELOCATABLE_TYPE);
@@ -1516,7 +1561,8 @@ public:
enum Format {
SDR,
HDRExtendedSrgbLinear,
- HDR10
+ HDR10,
+ HDRExtendedDisplayP3Linear
};
enum StereoTargetBuffer {
@@ -1770,7 +1816,8 @@ public:
EnableDebugMarkers = 1 << 0,
PreferSoftwareRenderer = 1 << 1,
EnablePipelineCacheDataSave = 1 << 2,
- EnableTimestamps = 1 << 3
+ EnableTimestamps = 1 << 3,
+ SuppressSmokeTestWarnings = 1 << 4
};
Q_DECLARE_FLAGS(Flags, Flag)
@@ -1821,7 +1868,10 @@ public:
OneDimensionalTextureMipmaps,
HalfAttributes,
RenderToOneDimensionalTexture,
- ThreeDimensionalTextureMipmaps
+ ThreeDimensionalTextureMipmaps,
+ MultiView,
+ TextureViewFormat,
+ ResolveDepthStencil
};
enum BeginFrameFlag {
@@ -1866,6 +1916,8 @@ public:
using CleanupCallback = std::function<void(QRhi *)>;
void addCleanupCallback(const CleanupCallback &callback);
+ void addCleanupCallback(const void *key, const CleanupCallback &callback);
+ void removeCleanupCallback(const void *key);
void runCleanup();
QRhiGraphicsPipeline *newGraphicsPipeline();
@@ -1942,7 +1994,7 @@ public:
const QRhiNativeHandles *nativeHandles();
bool makeThreadLocalNativeContextCurrent();
- static const int MAX_MIP_LEVELS = 16; // -> max width or height is 65536
+ static constexpr int MAX_MIP_LEVELS = 16; // -> max width or height is 65536
void releaseCachedResources();
diff --git a/src/gui/rhi/qrhi_p.h b/src/gui/rhi/qrhi_p.h
index 05df169a35..b5429372a8 100644
--- a/src/gui/rhi/qrhi_p.h
+++ b/src/gui/rhi/qrhi_p.h
@@ -149,6 +149,7 @@ public:
QSize *blockDim) const;
void textureFormatInfo(QRhiTexture::Format format, const QSize &size,
quint32 *bpl, quint32 *byteSize, quint32 *bytesPerPixel) const;
+ bool isStencilSupportingFormat(QRhiTexture::Format format) const;
void registerResource(QRhiResource *res, bool ownsNativeResources = true)
{
@@ -178,6 +179,16 @@ public:
cleanupCallbacks.append(callback);
}
+ void addCleanupCallback(const void *key, const QRhi::CleanupCallback &callback)
+ {
+ keyedCleanupCallbacks[key] = callback;
+ }
+
+ void removeCleanupCallback(const void *key)
+ {
+ keyedCleanupCallbacks.remove(key);
+ }
+
bool sanityCheckGraphicsPipeline(QRhiGraphicsPipeline *ps);
bool sanityCheckShaderResourceBindings(QRhiShaderResourceBindings *srb);
void updateLayoutDesc(QRhiShaderResourceBindings *srb);
@@ -221,6 +232,8 @@ public:
return a.d.binding < b.d.binding;
}
+ int effectiveSampleCount(int sampleCount) const;
+
QRhi *q;
static const int MAX_SHADER_CACHE_ENTRIES = 128;
@@ -238,6 +251,7 @@ private:
QHash<QRhiResource *, bool> resources;
QSet<QRhiResource *> pendingDeleteResources;
QVarLengthArray<QRhi::CleanupCallback, 4> cleanupCallbacks;
+ QHash<const void *, QRhi::CleanupCallback> keyedCleanupCallbacks;
QElapsedTimer pipelineCreationTimer;
qint64 accumulatedPipelineCreationTime = 0;
diff --git a/src/gui/rhi/qrhi_platform.h b/src/gui/rhi/qrhi_platform.h
index 30676d0da6..e7be522c52 100644
--- a/src/gui/rhi/qrhi_platform.h
+++ b/src/gui/rhi/qrhi_platform.h
@@ -23,7 +23,7 @@
#include <QtGui/qvulkaninstance.h>
#endif
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
Q_FORWARD_DECLARE_OBJC_CLASS(MTLDevice);
Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandQueue);
Q_FORWARD_DECLARE_OBJC_CLASS(MTLCommandBuffer);
@@ -66,7 +66,7 @@ struct Q_GUI_EXPORT QRhiGles2NativeHandles : public QRhiNativeHandles
#endif // opengl/qdoc
-#if QT_CONFIG(vulkan) || defined(Q_QDOC)
+#if (QT_CONFIG(vulkan) && __has_include(<vulkan/vulkan.h>)) || defined(Q_QDOC)
struct Q_GUI_EXPORT QRhiVulkanInitParams : public QRhiInitParams
{
@@ -145,12 +145,12 @@ struct Q_GUI_EXPORT QRhiD3D12NativeHandles : public QRhiNativeHandles
struct Q_GUI_EXPORT QRhiD3D12CommandBufferNativeHandles : public QRhiNativeHandles
{
- void *commandList = nullptr; // ID3D12GraphicsCommandList
+ void *commandList = nullptr; // ID3D12GraphicsCommandList1
};
#endif // WIN/QDOC
-#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC)
+#if QT_CONFIG(metal) || defined(Q_QDOC)
struct Q_GUI_EXPORT QRhiMetalInitParams : public QRhiInitParams
{
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index f20c1f4072..b09baf57b2 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -6,11 +6,9 @@
#include "vs_test_p.h"
#include <QWindow>
#include <qmath.h>
-#include <private/qsystemlibrary_p.h>
#include <QtCore/qcryptographichash.h>
#include <QtCore/private/qsystemerror_p.h>
-
-#include <d3dcompiler.h>
+#include "qrhid3dhelpers_p.h"
QT_BEGIN_NAMESPACE
@@ -32,7 +30,7 @@ using namespace Qt::StringLiterals;
\since 6.6
\brief Direct3D 11 specific initialization parameters.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A D3D11-based QRhi needs no special parameters for initialization. If
@@ -89,28 +87,53 @@ using namespace Qt::StringLiterals;
\c{d3d11.h} headers is not acceptable here. The actual types are
\c{ID3D11Device *} and \c{ID3D11DeviceContext *}.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
/*!
\variable QRhiD3D11NativeHandles::dev
+
+ Points to a
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11device}{ID3D11Device}
+ or left set to \nullptr if no existing device is to be imported.
+
+ \note When importing a device, both the device and the device context must be set to valid objects.
*/
/*!
\variable QRhiD3D11NativeHandles::context
+
+ Points to a \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11devicecontext}{ID3D11DeviceContext}
+ or left set to \nullptr if no existing device context is to be imported.
+
+ \note When importing a device, both the device and the device context must be set to valid objects.
*/
/*!
\variable QRhiD3D11NativeHandles::featureLevel
+
+ Specifies the feature level passed to
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-d3d11createdevice}{D3D11CreateDevice()}.
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context. When not set, the default rules outlined in the D3D
+ documentation apply.
*/
/*!
\variable QRhiD3D11NativeHandles::adapterLuidLow
+
+ The low part of the local identifier (LUID) of the DXGI adapter to use.
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context.
*/
/*!
\variable QRhiD3D11NativeHandles::adapterLuidHigh
+
+ The high part of the local identifier (LUID) of the DXGI adapter to use.
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context.
*/
// help mingw with its ancient sdk headers
@@ -194,13 +217,14 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (qEnvironmentVariableIntValue("QT_D3D_FLIP_DISCARD"))
qWarning("The default swap effect is FLIP_DISCARD, QT_D3D_FLIP_DISCARD is now ignored");
- if (qEnvironmentVariableIntValue("QT_D3D_NO_FLIP"))
- qWarning("Non-FLIP swapchains are no longer supported, QT_D3D_NO_FLIP is now ignored");
+ // Support for flip model swapchains is required now (since we are
+ // targeting Windows 10+), but the option for using the old model is still
+ // there. (some features are not supported then, however)
+ useLegacySwapchainModel = qEnvironmentVariableIntValue("QT_D3D_NO_FLIP");
- qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s",
- supportsAllowTearing ? "true" : "false");
-
- qCDebug(QRHI_LOG_INFO, "Default swap effect: FLIP_DISCARD");
+ qCDebug(QRHI_LOG_INFO, "FLIP_* swapchain supported = true, ALLOW_TEARING supported = %s, use legacy (non-FLIP) model = %s",
+ supportsAllowTearing ? "true" : "false",
+ useLegacySwapchainModel ? "true" : "false");
if (!importedDeviceAndContext) {
IDXGIAdapter1 *adapter;
@@ -249,9 +273,7 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
activeAdapter = adapter;
adapterLuid = desc.AdapterLuid;
- driverInfoStruct.deviceName = name.toUtf8();
- driverInfoStruct.deviceId = desc.DeviceId;
- driverInfoStruct.vendorId = desc.VendorId;
+ QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
qCDebug(QRHI_LOG_INFO, " using this adapter");
} else {
adapter->Release();
@@ -294,21 +316,24 @@ bool QRhiD3D11::create(QRhi::Flags flags)
return false;
}
+ const bool supports11_1 = SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void **>(&context)));
+ ctx->Release();
+ if (!supports11_1) {
+ qWarning("ID3D11DeviceContext1 not supported");
+ return false;
+ }
+
// Test if creating a Shader Model 5.0 vertex shader works; we want to
// fail already in create() if that's not the case.
ID3D11VertexShader *testShader = nullptr;
if (SUCCEEDED(dev->CreateVertexShader(g_testVertexShader, sizeof(g_testVertexShader), nullptr, &testShader))) {
testShader->Release();
} else {
- qWarning("D3D11 smoke test failed (failed to create vertex shader)");
- ctx->Release();
- return false;
- }
-
- const bool supports11_1 = SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void **>(&context)));
- ctx->Release();
- if (!supports11_1) {
- qWarning("ID3D11DeviceContext1 not supported");
+ static const char *msg = "D3D11 smoke test: Failed to create vertex shader";
+ if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+ qCDebug(QRHI_LOG_INFO, "%s", msg);
+ else
+ qWarning("%s", msg);
return false;
}
@@ -318,11 +343,19 @@ bool QRhiD3D11::create(QRhi::Flags flags)
// still not support this D3D_FEATURE_LEVEL_11_1 feature. (e.g.
// because it only does 11_0)
if (!features.ConstantBufferOffsetting) {
- qWarning("Constant buffer offsetting is not supported by the driver");
+ static const char *msg = "D3D11 smoke test: Constant buffer offsetting is not supported by the driver";
+ if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+ qCDebug(QRHI_LOG_INFO, "%s", msg);
+ else
+ qWarning("%s", msg);
return false;
}
} else {
- qWarning("Failed to query D3D11_FEATURE_D3D11_OPTIONS");
+ static const char *msg = "D3D11 smoke test: Failed to query D3D11_FEATURE_D3D11_OPTIONS";
+ if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+ qCDebug(QRHI_LOG_INFO, "%s", msg);
+ else
+ qWarning("%s", msg);
return false;
}
} else {
@@ -332,12 +365,14 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (SUCCEEDED(dev->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void **>(&dxgiDev)))) {
IDXGIAdapter *adapter = nullptr;
if (SUCCEEDED(dxgiDev->GetAdapter(&adapter))) {
- DXGI_ADAPTER_DESC desc;
- adapter->GetDesc(&desc);
- adapterLuid = desc.AdapterLuid;
- driverInfoStruct.deviceName = QString::fromUtf16(reinterpret_cast<char16_t *>(desc.Description)).toUtf8();
- driverInfoStruct.deviceId = desc.DeviceId;
- driverInfoStruct.vendorId = desc.VendorId;
+ IDXGIAdapter1 *adapter1 = nullptr;
+ if (SUCCEEDED(adapter->QueryInterface(__uuidof(IDXGIAdapter1), reinterpret_cast<void **>(&adapter1)))) {
+ DXGI_ADAPTER_DESC1 desc;
+ adapter1->GetDesc1(&desc);
+ adapterLuid = desc.AdapterLuid;
+ QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
+ adapter1->Release();
+ }
adapter->Release();
}
dxgiDev->Release();
@@ -348,11 +383,6 @@ bool QRhiD3D11::create(QRhi::Flags flags)
if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast<void **>(&annotations))))
annotations = nullptr;
- if (flags.testFlag(QRhi::EnableTimestamps)) {
- ofr.timestamps.prepare(2, this);
- // timestamp queries are optional so we can go on even if they failed
- }
-
deviceLost = false;
nativeHandlesStruct.dev = dev;
@@ -378,7 +408,16 @@ void QRhiD3D11::destroy()
clearShaderCache();
- ofr.timestamps.destroy();
+ if (ofr.tsDisjointQuery) {
+ ofr.tsDisjointQuery->Release();
+ ofr.tsDisjointQuery = nullptr;
+ }
+ for (int i = 0; i < 2; ++i) {
+ if (ofr.tsQueries[i]) {
+ ofr.tsQueries[i]->Release();
+ ofr.tsQueries[i] = nullptr;
+ }
+ }
if (annotations) {
annotations->Release();
@@ -427,19 +466,13 @@ QList<int> QRhiD3D11::supportedSampleCounts() const
return { 1, 2, 4, 8 };
}
-DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleCount(int sampleCount) const
+DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleDesc(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;
- }
+ const int s = effectiveSampleCount(sampleCount);
desc.Count = UINT(s);
if (s > 1)
@@ -588,6 +621,12 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
+ case QRhi::MultiView:
+ return false;
+ case QRhi::TextureViewFormat:
+ return false; // because we use fully typed formats for textures and relaxed casting is a D3D12 thing
+ case QRhi::ResolveDepthStencil:
+ return false;
default:
Q_UNREACHABLE();
return false;
@@ -1258,7 +1297,6 @@ const QRhiNativeHandles *QRhiD3D11::nativeHandles(QRhiCommandBuffer *cb)
void QRhiD3D11::beginExternal(QRhiCommandBuffer *cb)
{
QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
- // no timestampSwapChain, in order to avoid timestamp mess
executeCommandBuffer(cbD);
cbD->resetCommands();
}
@@ -1281,6 +1319,19 @@ double QRhiD3D11::lastCompletedGpuTime(QRhiCommandBuffer *cb)
return cbD->lastGpuTime;
}
+static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
+{
+ switch (rt->resourceType()) {
+ case QRhiResource::SwapChainRenderTarget:
+ return &QRHI_RES(QD3D11SwapChainRenderTarget, rt)->d;
+ case QRhiResource::TextureRenderTarget:
+ return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
+ default:
+ Q_UNREACHABLE();
+ return nullptr;
+ }
+}
+
QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
{
Q_UNUSED(flags);
@@ -1297,12 +1348,22 @@ QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
finishActiveReadbacks();
- if (swapChainD->timestamps.active[currentFrameSlot]) {
+ if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) {
double elapsedSec = 0;
- if (swapChainD->timestamps.tryQueryTimestamps(currentFrameSlot, context, &elapsedSec))
+ if (swapChainD->timestamps.tryQueryTimestamps(swapChainD->currentTimestampPairIndex, context, &elapsedSec))
swapChainD->cb.lastGpuTime = elapsedSec;
}
+ ID3D11Query *tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
+ ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
+ const bool recordTimestamps = tsStart && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
+
+ QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
+ cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
+ cmd.args.beginFrame.tsQuery = recordTimestamps ? tsStart : nullptr;
+ cmd.args.beginFrame.tsDisjointQuery = recordTimestamps ? tsDisjoint : nullptr;
+ cmd.args.beginFrame.swapchainData = rtData(&swapChainD->rt);
+
return QRhi::FrameOpSuccess;
}
@@ -1312,17 +1373,13 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
Q_ASSERT(contextState.currentSwapChain = swapChainD);
const int currentFrameSlot = swapChainD->currentFrameSlot;
- ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[currentFrameSlot];
- const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
- ID3D11Query *tsStart = swapChainD->timestamps.query[tsIdx];
- ID3D11Query *tsEnd = swapChainD->timestamps.query[tsIdx + 1];
- const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestamps.active[currentFrameSlot];
+ QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
+ cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
+ cmd.args.endFrame.tsQuery = nullptr; // done later manually, see below
+ cmd.args.endFrame.tsDisjointQuery = nullptr;
// send all commands to the context
- if (recordTimestamps)
- executeCommandBuffer(&swapChainD->cb, swapChainD);
- else
- executeCommandBuffer(&swapChainD->cb);
+ executeCommandBuffer(&swapChainD->cb);
if (swapChainD->sampleDesc.Count > 1) {
context->ResolveSubresource(swapChainD->backBufferTex, 0,
@@ -1330,17 +1387,25 @@ QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
swapChainD->colorFormat);
}
- // this is here because we want to include the time spent on the resolve as well
+ // this is here because we want to include the time spent on the ResolveSubresource as well
+ ID3D11Query *tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
+ ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
+ const bool recordTimestamps = tsEnd && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
if (recordTimestamps) {
context->End(tsEnd);
context->End(tsDisjoint);
- swapChainD->timestamps.active[currentFrameSlot] = true;
+ swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true;
+ swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QD3D11SwapChainTimestamps::TIMESTAMP_PAIRS;
}
if (!flags.testFlag(QRhi::SkipPresent)) {
UINT presentFlags = 0;
if (swapChainD->swapInterval == 0 && (swapChainD->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING))
presentFlags |= DXGI_PRESENT_ALLOW_TEARING;
+ if (!swapChainD->swapChain) {
+ qWarning("Failed to present: IDXGISwapChain is unavailable");
+ return QRhi::FrameOpError;
+ }
HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
qWarning("Device loss detected in Present()");
@@ -1375,12 +1440,36 @@ QRhi::FrameOpResult QRhiD3D11::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
ofr.cbWrapper.resetState();
*cb = &ofr.cbWrapper;
- if (ofr.timestamps.active[ofr.timestampIdx]) {
- double elapsedSec = 0;
- if (ofr.timestamps.tryQueryTimestamps(ofr.timestampIdx, context, &elapsedSec))
- ofr.cbWrapper.lastGpuTime = elapsedSec;
+ if (rhiFlags.testFlag(QRhi::EnableTimestamps)) {
+ D3D11_QUERY_DESC queryDesc = {};
+ if (!ofr.tsDisjointQuery) {
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
+ HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsDisjointQuery);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp disjoint query: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return QRhi::FrameOpError;
+ }
+ }
+ queryDesc.Query = D3D11_QUERY_TIMESTAMP;
+ for (int i = 0; i < 2; ++i) {
+ if (!ofr.tsQueries[i]) {
+ HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsQueries[i]);
+ if (FAILED(hr)) {
+ qWarning("Failed to create timestamp query: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return QRhi::FrameOpError;
+ }
+ }
+ }
}
+ QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
+ cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
+ cmd.args.beginFrame.tsQuery = ofr.tsQueries[0] ? ofr.tsQueries[0] : nullptr;
+ cmd.args.beginFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
+ cmd.args.beginFrame.swapchainData = nullptr;
+
return QRhi::FrameOpSuccess;
}
@@ -1389,25 +1478,39 @@ QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame(QRhi::EndFrameFlags flags)
Q_UNUSED(flags);
ofr.active = false;
- ID3D11Query *tsDisjoint = ofr.timestamps.disjointQuery[ofr.timestampIdx];
- ID3D11Query *tsStart = ofr.timestamps.query[ofr.timestampIdx * 2];
- ID3D11Query *tsEnd = ofr.timestamps.query[ofr.timestampIdx * 2 + 1];
- const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !ofr.timestamps.active[ofr.timestampIdx];
- if (recordTimestamps) {
- context->Begin(tsDisjoint);
- context->End(tsStart); // record timestamp; no Begin() for D3D11_QUERY_TIMESTAMP
- }
+ QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
+ cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
+ cmd.args.endFrame.tsQuery = ofr.tsQueries[1] ? ofr.tsQueries[1] : nullptr;
+ cmd.args.endFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
executeCommandBuffer(&ofr.cbWrapper);
context->Flush();
finishActiveReadbacks();
- if (recordTimestamps) {
- context->End(tsEnd);
- context->End(tsDisjoint);
- ofr.timestamps.active[ofr.timestampIdx] = true;
- ofr.timestampIdx = (ofr.timestampIdx + 1) % 2;
+ if (ofr.tsQueries[0]) {
+ quint64 timestamps[2];
+ D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+ HRESULT hr;
+ bool ok = true;
+ do {
+ hr = context->GetData(ofr.tsDisjointQuery, &dj, sizeof(dj), 0);
+ } while (hr == S_FALSE);
+ ok &= hr == S_OK;
+ do {
+ hr = context->GetData(ofr.tsQueries[1], &timestamps[1], sizeof(quint64), 0);
+ } while (hr == S_FALSE);
+ ok &= hr == S_OK;
+ do {
+ hr = context->GetData(ofr.tsQueries[0], &timestamps[0], sizeof(quint64), 0);
+ } while (hr == S_FALSE);
+ ok &= hr == S_OK;
+ if (ok) {
+ if (!dj.Disjoint && dj.Frequency) {
+ const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
+ ofr.cbWrapper.lastGpuTime = elapsedMs / 1000.0;
+ }
+ }
}
return QRhi::FrameOpSuccess;
@@ -1447,9 +1550,9 @@ static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTex
case QRhiTexture::D16:
return DXGI_FORMAT_R16_TYPELESS;
case QRhiTexture::D24:
- return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+ return DXGI_FORMAT_R24G8_TYPELESS;
case QRhiTexture::D24S8:
- return DXGI_FORMAT_D24_UNORM_S8_UINT;
+ return DXGI_FORMAT_R24G8_TYPELESS;
case QRhiTexture::D32F:
return DXGI_FORMAT_R32_TYPELESS;
@@ -1550,7 +1653,7 @@ QRhi::FrameOpResult QRhiD3D11::finish()
} else {
Q_ASSERT(contextState.currentSwapChain);
Q_ASSERT(contextState.currentSwapChain->cb.recordingPass == QD3D11CommandBuffer::NoPass);
- executeCommandBuffer(&contextState.currentSwapChain->cb); // no timestampSwapChain, in order to avoid timestamp mess
+ executeCommandBuffer(&contextState.currentSwapChain->cb);
contextState.currentSwapChain->cb.resetCommands();
}
}
@@ -1925,19 +2028,6 @@ void QRhiD3D11::finishActiveReadbacks()
f();
}
-static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
-{
- switch (rt->resourceType()) {
- case QRhiResource::SwapChainRenderTarget:
- return &QRHI_RES(QD3D11SwapChainRenderTarget, 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);
@@ -2056,6 +2146,8 @@ void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, UINT(colorAtt.layer()), 1);
cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
}
+ if (rtTex->m_desc.depthResolveTexture())
+ qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
}
cbD->recordingPass = QD3D11CommandBuffer::NoPass;
@@ -2643,7 +2735,7 @@ void QRhiD3D11::resetShaderResources()
currentShaderMask &= ~StageU##MaskBit; \
}
-void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain)
+void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD)
{
quint32 stencilRef = 0;
float blendConstants[] = { 1, 1, 1, 1 };
@@ -2656,26 +2748,30 @@ void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *
};
int currentShaderMask = 0xFF;
- if (timestampSwapChain) {
- const int currentFrameSlot = timestampSwapChain->currentFrameSlot;
- ID3D11Query *tsDisjoint = timestampSwapChain->timestamps.disjointQuery[currentFrameSlot];
- const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot;
- ID3D11Query *tsStart = timestampSwapChain->timestamps.query[tsIdx];
- if (tsDisjoint && tsStart && !timestampSwapChain->timestamps.active[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(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
- context->End(tsStart); // just record a timestamp, no Begin needed
- }
- }
-
for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) {
const QD3D11CommandBuffer::Command &cmd(*it);
switch (cmd.cmd) {
+ case QD3D11CommandBuffer::Command::BeginFrame:
+ if (cmd.args.beginFrame.tsDisjointQuery)
+ context->Begin(cmd.args.beginFrame.tsDisjointQuery);
+ if (cmd.args.beginFrame.tsQuery) {
+ if (cmd.args.beginFrame.swapchainData) {
+ // 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.
+ QD3D11RenderTargetData *rtD = cmd.args.beginFrame.swapchainData;
+ context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+ }
+ context->End(cmd.args.beginFrame.tsQuery); // no Begin() for D3D11_QUERY_TIMESTAMP
+ }
+ break;
+ case QD3D11CommandBuffer::Command::EndFrame:
+ if (cmd.args.endFrame.tsQuery)
+ context->End(cmd.args.endFrame.tsQuery);
+ if (cmd.args.endFrame.tsDisjointQuery)
+ context->End(cmd.args.endFrame.tsDisjointQuery);
+ break;
case QD3D11CommandBuffer::Command::ResetShaderResources:
resetShaderResources();
break;
@@ -3029,7 +3125,7 @@ bool QD3D11RenderBuffer::create()
return false;
QRHI_RES_RHI(QRhiD3D11);
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = UINT(m_pixelSize.width());
@@ -3171,7 +3267,7 @@ static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
case QRhiTexture::Format::D16:
return DXGI_FORMAT_D16_UNORM;
case QRhiTexture::Format::D24:
- return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+ return DXGI_FORMAT_D24_UNORM_S8_UINT;
case QRhiTexture::Format::D24S8:
return DXGI_FORMAT_D24_UNORM_S8_UINT;
case QRhiTexture::Format::D32F:
@@ -3200,7 +3296,7 @@ bool QD3D11Texture::prepareCreate(QSize *adjustedSize)
QRHI_RES_RHI(QRhiD3D11);
dxgiFormat = toD3DTextureFormat(m_format, m_flags);
mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
if (sampleDesc.Count > 1) {
if (isCube) {
qWarning("Cubemap texture cannot be multisample");
@@ -3834,6 +3930,27 @@ bool QD3D11TextureRenderTarget::create()
dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
: D3D11_DSV_DIMENSION_TEXTURE2D;
+ if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
+ if (depthTexD->sampleDesc.Count > 1) {
+ dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY;
+ if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+ dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+ dsvDesc.Texture2DMSArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+ } else {
+ dsvDesc.Texture2DMSArray.FirstArraySlice = 0;
+ dsvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+ }
+ } else {
+ dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
+ if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+ dsvDesc.Texture2DArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+ dsvDesc.Texture2DArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+ } else {
+ dsvDesc.Texture2DArray.FirstArraySlice = 0;
+ dsvDesc.Texture2DArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+ }
+ }
+ }
HRESULT hr = rhiD->dev->CreateDepthStencilView(depthTexD->tex, &dsvDesc, &dsv);
if (FAILED(hr)) {
qWarning("Failed to create dsv: %s",
@@ -4128,6 +4245,22 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
return DXGI_FORMAT_R16G16_FLOAT;
case QRhiVertexInputAttribute::Half:
return DXGI_FORMAT_R16_FLOAT;
+ case QRhiVertexInputAttribute::UShort4:
+ // Note: D3D does not support UShort3. Pass through UShort3 as UShort4.
+ case QRhiVertexInputAttribute::UShort3:
+ return DXGI_FORMAT_R16G16B16A16_UINT;
+ case QRhiVertexInputAttribute::UShort2:
+ return DXGI_FORMAT_R16G16_UINT;
+ case QRhiVertexInputAttribute::UShort:
+ return DXGI_FORMAT_R16_UINT;
+ case QRhiVertexInputAttribute::SShort4:
+ // Note: D3D does not support SShort3. Pass through SShort3 as SShort4.
+ case QRhiVertexInputAttribute::SShort3:
+ return DXGI_FORMAT_R16G16B16A16_SINT;
+ case QRhiVertexInputAttribute::SShort2:
+ return DXGI_FORMAT_R16G16_SINT;
+ case QRhiVertexInputAttribute::SShort:
+ return DXGI_FORMAT_R16_SINT;
default:
Q_UNREACHABLE();
return DXGI_FORMAT_R32G32B32A32_FLOAT;
@@ -4240,18 +4373,6 @@ static inline D3D11_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
}
}
-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 inline QByteArray sourceHash(const QByteArray &source)
{
// taken from the GL backend, use the same mechanism to get a key
@@ -4317,7 +4438,7 @@ QByteArray QRhiD3D11::compileHlslShaderSource(const QShader &shader, QShader::Va
return cacheIt.value();
}
- static const pD3DCompile d3dCompile = resolveD3DCompile();
+ static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile();
if (d3dCompile == nullptr) {
qWarning("Unable to resolve function D3DCompile()");
return QByteArray();
@@ -4367,7 +4488,7 @@ bool QD3D11GraphicsPipeline::create()
rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias;
rastDesc.DepthClipEnable = true;
rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
- rastDesc.MultisampleEnable = rhiD->effectiveSampleCount(m_sampleCount).Count > 1;
+ rastDesc.MultisampleEnable = rhiD->effectiveSampleDesc(m_sampleCount).Count > 1;
HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
if (FAILED(hr)) {
qWarning("Failed to create rasterizer state: %s",
@@ -4685,14 +4806,13 @@ void QD3D11CommandBuffer::destroy()
// nothing to do here
}
-bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
+bool QD3D11SwapChainTimestamps::prepare(QRhiD3D11 *rhiD)
{
// Creates the query objects if not yet done, but otherwise calling this
// function is expected to be a no-op.
- Q_ASSERT(pairCount <= MAX_TIMESTAMP_PAIRS);
D3D11_QUERY_DESC queryDesc = {};
- for (int i = 0; i < pairCount; ++i) {
+ for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
if (!disjointQuery[i]) {
queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &disjointQuery[i]);
@@ -4704,7 +4824,7 @@ bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
}
queryDesc.Query = D3D11_QUERY_TIMESTAMP;
for (int j = 0; j < 2; ++j) {
- const int idx = pairCount * i + j;
+ const int idx = 2 * i + j;
if (!query[idx]) {
HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &query[idx]);
if (FAILED(hr)) {
@@ -4715,20 +4835,19 @@ bool QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD)
}
}
}
- this->pairCount = pairCount;
return true;
}
-void QD3D11Timestamps::destroy()
+void QD3D11SwapChainTimestamps::destroy()
{
- for (int i = 0; i < MAX_TIMESTAMP_PAIRS; ++i) {
+ for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
active[i] = false;
if (disjointQuery[i]) {
disjointQuery[i]->Release();
disjointQuery[i] = nullptr;
}
for (int j = 0; j < 2; ++j) {
- const int idx = MAX_TIMESTAMP_PAIRS * i + j;
+ const int idx = TIMESTAMP_PAIRS * i + j;
if (query[idx]) {
query[idx]->Release();
query[idx] = nullptr;
@@ -4737,26 +4856,21 @@ void QD3D11Timestamps::destroy()
}
}
-bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec)
+bool QD3D11SwapChainTimestamps::tryQueryTimestamps(int pairIndex, ID3D11DeviceContext *context, double *elapsedSec)
{
bool result = false;
- if (!active[idx])
+ if (!active[pairIndex])
return result;
- ID3D11Query *tsDisjoint = disjointQuery[idx];
- const int tsIdx = pairCount * idx;
- ID3D11Query *tsStart = query[tsIdx];
- ID3D11Query *tsEnd = query[tsIdx + 1];
+ ID3D11Query *tsDisjoint = disjointQuery[pairIndex];
+ ID3D11Query *tsStart = query[pairIndex * 2];
+ ID3D11Query *tsEnd = query[pairIndex * 2 + 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) {
@@ -4765,16 +4879,14 @@ bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context,
*elapsedSec = elapsedMs / 1000.0;
result = true;
}
- active[idx] = false;
- } // else leave active set, will retry in a subsequent beginFrame or similar
+ active[pairIndex] = false;
+ } // else leave active set, will retry in a subsequent beginFrame
return result;
}
QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi)
- : QRhiSwapChain(rhi),
- rt(rhi, this),
- cb(rhi)
+ : QRhiSwapChain(rhi), rt(rhi, this), rtRight(rhi, this), cb(rhi)
{
backBufferTex = nullptr;
backBufferRtv = nullptr;
@@ -4795,6 +4907,10 @@ void QD3D11SwapChain::releaseBuffers()
backBufferRtv->Release();
backBufferRtv = nullptr;
}
+ if (backBufferRtvRight) {
+ backBufferRtvRight->Release();
+ backBufferRtvRight = nullptr;
+ }
if (backBufferTex) {
backBufferTex->Release();
backBufferTex = nullptr;
@@ -4834,8 +4950,12 @@ void QD3D11SwapChain::destroy()
}
QRHI_RES_RHI(QRhiD3D11);
- if (rhiD)
+ if (rhiD) {
rhiD->unregisterResource(this);
+ // See Deferred Destruction Issues with Flip Presentation Swap Chains in
+ // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-flush
+ rhiD->context->Flush();
+ }
}
QRhiCommandBuffer *QD3D11SwapChain::currentFrameCommandBuffer()
@@ -4848,48 +4968,15 @@ QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget()
return &rt;
}
-QSize QD3D11SwapChain::surfacePixelSize()
+QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
{
- Q_ASSERT(m_window);
- return m_window->size() * m_window->devicePixelRatio();
+ return targetBuffer == StereoTargetBuffer::LeftBuffer? &rt: &rtRight;
}
-static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
-{
- bool ok = false;
- QRect wr = w->geometry();
- wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
- const QPoint center = wr.center();
- IDXGIOutput *currentOutput = nullptr;
- IDXGIOutput *output = nullptr;
- for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
- DXGI_OUTPUT_DESC desc;
- output->GetDesc(&desc);
- const RECT r = desc.DesktopCoordinates;
- const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
- if (dr.contains(center)) {
- currentOutput = output;
- break;
- } else {
- output->Release();
- }
- }
- if (currentOutput) {
- ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
- currentOutput->Release();
- }
- return ok;
-}
-
-static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
+QSize QD3D11SwapChain::surfacePixelSize()
{
- bool ok = false;
- IDXGIOutput6 *out6 = nullptr;
- if (output6ForWindow(w, adapter, &out6)) {
- ok = SUCCEEDED(out6->GetDesc1(result));
- out6->Release();
- }
- return ok;
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
}
bool QD3D11SwapChain::isFormatSupported(Format f)
@@ -4904,7 +4991,7 @@ bool QD3D11SwapChain::isFormatSupported(Format f)
QRHI_RES_RHI(QRhiD3D11);
DXGI_OUTPUT_DESC1 desc1;
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
}
@@ -4915,14 +5002,16 @@ bool QD3D11SwapChain::isFormatSupported(Format f)
QRhiSwapChainHdrInfo QD3D11SwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
- if (m_format != QRhiSwapChain::SDR && m_window) {
+ // Must use m_window, not window, given this may be called before createOrResize().
+ if (m_window) {
QRHI_RES_RHI(QRhiD3D11);
DXGI_OUTPUT_DESC1 hdrOutputDesc;
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
- info.isHardCodedDefaults = false;
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
+ info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
+ info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
}
}
return info;
@@ -4972,26 +5061,19 @@ bool QD3D11SwapChain::newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI
return true;
}
-static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
-static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
-
bool QRhiD3D11::ensureDirectCompositionDevice()
{
if (dcompDevice)
return true;
qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)");
-
- HRESULT hr = DCompositionCreateDevice(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast<void **>(&dcompDevice));
- if (FAILED(hr)) {
- qWarning("Failed to Direct Composition device: %s",
- qPrintable(QSystemError::windowsComString(hr)));
- return false;
- }
-
- return true;
+ dcompDevice = QRhiD3D::createDirectCompositionDevice();
+ return dcompDevice ? true : false;
}
+static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
+static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+
bool QD3D11SwapChain::createOrResize()
{
// Can be called multiple times due to window resizes - that is not the
@@ -4999,6 +5081,7 @@ bool QD3D11SwapChain::createOrResize()
// resize the buffers then.
const bool needsRegistration = !window || window != m_window;
+ const bool stereo = m_window->format().stereo();
// except if the window actually changes
if (window && window != m_window)
@@ -5017,9 +5100,9 @@ bool QD3D11SwapChain::createOrResize()
QRHI_RES_RHI(QRhiD3D11);
if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
- if (rhiD->ensureDirectCompositionDevice()) {
+ if (!rhiD->useLegacySwapchainModel && rhiD->ensureDirectCompositionDevice()) {
if (!dcompTarget) {
- hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
+ hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, false, &dcompTarget);
if (FAILED(hr)) {
qWarning("Failed to create Direct Compsition target for the window: %s",
qPrintable(QSystemError::windowsComString(hr)));
@@ -5050,13 +5133,13 @@ bool QD3D11SwapChain::createOrResize()
swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
if (!swapChain) {
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
colorFormat = DEFAULT_FORMAT;
srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT;
DXGI_COLOR_SPACE_TYPE hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
DXGI_OUTPUT_DESC1 hdrOutputDesc;
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
// https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
switch (m_format) {
@@ -5096,8 +5179,9 @@ bool QD3D11SwapChain::createOrResize()
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = BUFFER_COUNT;
desc.Flags = swapChainFlags;
- desc.Scaling = DXGI_SCALING_NONE;
- desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ desc.Scaling = rhiD->useLegacySwapchainModel ? DXGI_SCALING_STRETCH : DXGI_SCALING_NONE;
+ desc.SwapEffect = rhiD->useLegacySwapchainModel ? DXGI_SWAP_EFFECT_DISCARD : DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ desc.Stereo = stereo;
if (dcompVisual) {
// With DirectComposition setting AlphaMode to STRAIGHT fails the
@@ -5157,14 +5241,19 @@ bool QD3D11SwapChain::createOrResize()
qWarning("Failed to set content for Direct Composition visual: %s",
qPrintable(QSystemError::windowsComString(hr)));
}
+ } else {
+ // disable Alt+Enter; not relevant when using DirectComposition
+ rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
}
}
if (FAILED(hr)) {
- qWarning("Failed to create D3D11 swapchain: %s",
- qPrintable(QSystemError::windowsComString(hr)));
+ qWarning("Failed to create D3D11 swapchain: %s"
+ " (Width=%u Height=%u Format=%u SampleCount=%u BufferCount=%u Scaling=%u SwapEffect=%u Stereo=%u)",
+ qPrintable(QSystemError::windowsComString(hr)),
+ desc.Width, desc.Height, UINT(desc.Format), desc.SampleDesc.Count,
+ desc.BufferCount, UINT(desc.Scaling), UINT(desc.SwapEffect), UINT(desc.Stereo));
return false;
}
- rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
} else {
releaseBuffers();
// flip model -> buffer count is the real buffer count, not 1 like with the legacy modes
@@ -5211,6 +5300,19 @@ bool QD3D11SwapChain::createOrResize()
return false;
}
+ if (stereo) {
+ // Create a second render target view for the right eye
+ rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+ rtvDesc.Texture2DArray.FirstArraySlice = 1;
+ rtvDesc.Texture2DArray.ArraySize = 1;
+ hr = rhiD->dev->CreateRenderTargetView(backBufferTex, &rtvDesc, &backBufferRtvRight);
+ if (FAILED(hr)) {
+ qWarning("Failed to create rtv for swapchain backbuffer (right eye): %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return false;
+ }
+ }
+
// Try to reduce stalls by having a dedicated MSAA texture per swapchain buffer.
for (int i = 0; i < BUFFER_COUNT; ++i) {
if (sampleDesc.Count > 1) {
@@ -5249,8 +5351,20 @@ bool QD3D11SwapChain::createOrResize()
rtD->d.colorAttCount = 1;
rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+ if (stereo) {
+ rtD = QRHI_RES(QD3D11SwapChainRenderTarget, &rtRight);
+ rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+ rtD->d.pixelSize = pixelSize;
+ rtD->d.dpr = float(window->devicePixelRatio());
+ rtD->d.sampleCount = int(sampleDesc.Count);
+ rtD->d.colorAttCount = 1;
+ rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+ rtD->d.rtv[0] = backBufferRtvRight;
+ rtD->d.dsv = ds ? ds->dsv : nullptr;
+ }
+
if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) {
- timestamps.prepare(BUFFER_COUNT, rhiD);
+ timestamps.prepare(rhiD);
// timestamp queries are optional so we can go on even if they failed
}
diff --git a/src/gui/rhi/qrhid3d11_p.h b/src/gui/rhi/qrhid3d11_p.h
index e410375029..7644748407 100644
--- a/src/gui/rhi/qrhid3d11_p.h
+++ b/src/gui/rhi/qrhid3d11_p.h
@@ -16,7 +16,7 @@
//
#include "qrhi_p.h"
-#include "qshaderdescription.h"
+#include <rhi/qshaderdescription.h>
#include <QWindow>
#include <d3d11_1.h>
@@ -356,6 +356,8 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
struct Command {
enum Cmd {
+ BeginFrame,
+ EndFrame,
ResetShaderResources,
SetRenderTarget,
Clear,
@@ -386,6 +388,15 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
// QRhiTexture/Buffer/etc. pointers).
union Args {
struct {
+ ID3D11Query *tsQuery;
+ ID3D11Query *tsDisjointQuery;
+ QD3D11RenderTargetData *swapchainData;
+ } beginFrame;
+ struct {
+ ID3D11Query *tsQuery;
+ ID3D11Query *tsDisjointQuery;
+ } endFrame;
+ struct {
QRhiRenderTarget *rt;
} setRenderTarget;
struct {
@@ -556,17 +567,15 @@ struct QD3D11CommandBuffer : public QRhiCommandBuffer
}
};
-static const int QD3D11_SWAPCHAIN_BUFFER_COUNT = 2;
-
-struct QD3D11Timestamps
+struct QD3D11SwapChainTimestamps
{
- static const int MAX_TIMESTAMP_PAIRS = QD3D11_SWAPCHAIN_BUFFER_COUNT;
- bool active[MAX_TIMESTAMP_PAIRS] = {};
- ID3D11Query *disjointQuery[MAX_TIMESTAMP_PAIRS] = {};
- ID3D11Query *query[MAX_TIMESTAMP_PAIRS * 2] = {};
- int pairCount = 0;
+ static const int TIMESTAMP_PAIRS = 2;
+
+ bool active[TIMESTAMP_PAIRS] = {};
+ ID3D11Query *disjointQuery[TIMESTAMP_PAIRS] = {};
+ ID3D11Query *query[TIMESTAMP_PAIRS * 2] = {};
- bool prepare(int pairCount, QRhiD3D11 *rhiD);
+ bool prepare(QRhiD3D11 *rhiD);
void destroy();
bool tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec);
};
@@ -579,6 +588,7 @@ struct QD3D11SwapChain : public QRhiSwapChain
QRhiCommandBuffer *currentFrameCommandBuffer() override;
QRhiRenderTarget *currentFrameRenderTarget() override;
+ QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
QSize surfacePixelSize() override;
bool isFormatSupported(Format f) override;
@@ -594,6 +604,7 @@ struct QD3D11SwapChain : public QRhiSwapChain
QWindow *window = nullptr;
QSize pixelSize;
QD3D11SwapChainRenderTarget rt;
+ QD3D11SwapChainRenderTarget rtRight;
QD3D11CommandBuffer cb;
DXGI_FORMAT colorFormat;
DXGI_FORMAT srgbAdjustedColorFormat;
@@ -601,7 +612,8 @@ struct QD3D11SwapChain : public QRhiSwapChain
UINT swapChainFlags = 0;
ID3D11Texture2D *backBufferTex;
ID3D11RenderTargetView *backBufferRtv;
- static const int BUFFER_COUNT = QD3D11_SWAPCHAIN_BUFFER_COUNT;
+ ID3D11RenderTargetView *backBufferRtvRight = nullptr;
+ static const int BUFFER_COUNT = 2;
ID3D11Texture2D *msaaTex[BUFFER_COUNT];
ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
DXGI_SAMPLE_DESC sampleDesc;
@@ -611,7 +623,8 @@ struct QD3D11SwapChain : public QRhiSwapChain
UINT swapInterval = 1;
IDCompositionTarget *dcompTarget = nullptr;
IDCompositionVisual *dcompVisual = nullptr;
- QD3D11Timestamps timestamps;
+ QD3D11SwapChainTimestamps timestamps;
+ int currentTimestampPairIndex = 0;
};
class QRhiD3D11 : public QRhiImplementation
@@ -736,8 +749,8 @@ public:
const uint *dynOfsPairs, int dynOfsPairCount,
bool offsetOnlyChange);
void resetShaderResources();
- void executeCommandBuffer(QD3D11CommandBuffer *cbD, QD3D11SwapChain *timestampSwapChain = nullptr);
- DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount) const;
+ void executeCommandBuffer(QD3D11CommandBuffer *cbD);
+ DXGI_SAMPLE_DESC effectiveSampleDesc(int sampleCount) const;
void finishActiveReadbacks();
void reportLiveObjects(ID3D11Device *device);
void clearShaderCache();
@@ -757,6 +770,7 @@ public:
IDXGIFactory1 *dxgiFactory = nullptr;
IDCompositionDevice *dcompDevice = nullptr;
bool supportsAllowTearing = false;
+ bool useLegacySwapchainModel = false;
bool deviceLost = false;
QRhiD3D11NativeHandles nativeHandlesStruct;
QRhiDriverInfo driverInfoStruct;
@@ -778,8 +792,8 @@ public:
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
QD3D11CommandBuffer cbWrapper;
- QD3D11Timestamps timestamps;
- int timestampIdx = 0;
+ ID3D11Query *tsQueries[2] = {};
+ ID3D11Query *tsDisjointQuery = nullptr;
} ofr;
struct TextureReadback {
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index 19843ed82b..0f176c683d 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -2,16 +2,10 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qrhid3d12_p.h"
-#include "qshader.h"
-#include <QWindow>
#include <qmath.h>
-#include <private/qsystemlibrary_p.h>
-#include <QtCore/qcryptographichash.h>
#include <QtCore/private/qsystemerror_p.h>
-
-#include <d3dcompiler.h>
#include <comdef.h>
-
+#include "qrhid3dhelpers_p.h"
#include "cs_mipmap_p.h"
#if __has_include(<pix.h>)
@@ -19,6 +13,8 @@
#define QRHI_D3D12_HAS_OLD_PIX
#endif
+#ifdef __ID3D12Device2_INTERFACE_DEFINED__
+
QT_BEGIN_NAMESPACE
/*
@@ -30,7 +26,7 @@ QT_BEGIN_NAMESPACE
\inmodule QtGui
\brief Direct3D 12 specific initialization parameters.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A D3D12-based QRhi needs no special parameters for initialization. If
@@ -81,34 +77,60 @@ QT_BEGIN_NAMESPACE
\c{d3d12.h} headers is not acceptable here. The actual types are
\c{ID3D12Device *} and \c{ID3D12CommandQueue *}.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
/*!
\variable QRhiD3D12NativeHandles::dev
+
+ Points to a
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nn-d3d12-id3d12device}{ID3D12Device}
+ or left set to \nullptr if no existing device is to be imported.
*/
/*!
\variable QRhiD3D12NativeHandles::minimumFeatureLevel
+
+ Specifies the \b minimum feature level passed to
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-d3d12createdevice}{D3D12CreateDevice()}.
+ When not set, \c{D3D_FEATURE_LEVEL_11_0} is used. See
+ \l{https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels}{this
+ page} for details.
+
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context.
*/
/*!
\variable QRhiD3D12NativeHandles::adapterLuidLow
+
+ The low part of the local identifier (LUID) of the DXGI adapter to use.
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context.
*/
/*!
\variable QRhiD3D12NativeHandles::adapterLuidHigh
+
+ The high part of the local identifier (LUID) of the DXGI adapter to use.
+ Relevant only when QRhi creates the device, ignored when importing a device
+ and device context.
*/
/*!
\variable QRhiD3D12NativeHandles::commandQueue
+
+ When set, must point to a
+ \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nn-d3d12-id3d12commandqueue}{ID3D12CommandQueue}.
+ It allows to optionally import a command queue as well, in addition to a
+ device.
*/
/*!
\class QRhiD3D12CommandBufferNativeHandles
\inmodule QtGui
- \brief Holds the ID3D12GraphicsCommandList object that is backing a QRhiCommandBuffer.
+ \brief Holds the ID3D12GraphicsCommandList1 object that is backing a QRhiCommandBuffer.
\note The command list object is only guaranteed to be valid, and
in recording state, while recording a frame. That is, between a
@@ -116,7 +138,7 @@ QT_BEGIN_NAMESPACE
\l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
\l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -132,8 +154,14 @@ QRhiD3D12::QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *import
debugLayer = params->enableDebugLayer;
if (importParams) {
if (importParams->dev) {
- dev = reinterpret_cast<ID3D12Device *>(importParams->dev);
- importedDevice = true;
+ ID3D12Device *d3d12Device = reinterpret_cast<ID3D12Device *>(importParams->dev);
+ if (SUCCEEDED(d3d12Device->QueryInterface(__uuidof(ID3D12Device2), reinterpret_cast<void **>(&dev)))) {
+ // get rid of the ref added by QueryInterface
+ d3d12Device->Release();
+ importedDevice = true;
+ } else {
+ qWarning("ID3D12Device2 not supported, cannot import device");
+ }
}
if (importParams->commandQueue) {
cmdQueue = reinterpret_cast<ID3D12CommandQueue *>(importParams->commandQueue);
@@ -179,9 +207,20 @@ bool QRhiD3D12::create(QRhi::Flags flags)
factoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
if (FAILED(hr)) {
- qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
- qPrintable(QSystemError::windowsComString(hr)));
- return false;
+ // retry without debug, if it was requested (to match D3D11 backend behavior)
+ if (debugLayer) {
+ qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
+ "Attempting to create DXGIFactory2 without it.");
+ factoryFlags &= ~DXGI_CREATE_FACTORY_DEBUG;
+ hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+ }
+ if (SUCCEEDED(hr)) {
+ debugLayer = false;
+ } else {
+ qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return false;
+ }
}
supportsAllowTearing = false;
@@ -249,9 +288,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
activeAdapter = adapter;
adapterLuid = desc.AdapterLuid;
- driverInfoStruct.deviceName = name.toUtf8();
- driverInfoStruct.deviceId = desc.DeviceId;
- driverInfoStruct.vendorId = desc.VendorId;
+ QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
qCDebug(QRHI_LOG_INFO, " using this adapter");
} else {
adapter->Release();
@@ -267,7 +304,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
hr = D3D12CreateDevice(activeAdapter,
minimumFeatureLevel,
- __uuidof(ID3D12Device),
+ __uuidof(ID3D12Device2),
reinterpret_cast<void **>(&dev));
if (FAILED(hr)) {
qWarning("Failed to create D3D12 device: %s", qPrintable(QSystemError::windowsComString(hr)));
@@ -281,16 +318,20 @@ bool QRhiD3D12::create(QRhi::Flags flags)
for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
- adapter->Release();
if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
&& desc.AdapterLuid.HighPart == adapterLuid.HighPart)
{
- driverInfoStruct.deviceName = QString::fromUtf16(reinterpret_cast<char16_t *>(desc.Description)).toUtf8();
- driverInfoStruct.deviceId = desc.DeviceId;
- driverInfoStruct.vendorId = desc.VendorId;
+ activeAdapter = adapter;
+ QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
break;
+ } else {
+ adapter->Release();
}
}
+ if (!activeAdapter) {
+ qWarning("No adapter");
+ return false;
+ }
qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
}
@@ -392,6 +433,9 @@ bool QRhiD3D12::create(QRhi::Flags flags)
qWarning("Could not create host-visible staging area");
return false;
}
+ QString decoratedName = QLatin1String("Small staging area buffer/");
+ decoratedName += QString::number(i);
+ smallStagingAreas[i].mem.buffer->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
}
if (!shaderVisibleCbvSrvUavHeap.create(dev,
@@ -402,6 +446,53 @@ bool QRhiD3D12::create(QRhi::Flags flags)
return false;
}
+ if (flags.testFlag(QRhi::EnableTimestamps)) {
+ static bool wantsStablePowerState = qEnvironmentVariableIntValue("QT_D3D_STABLE_POWER_STATE");
+ //
+ // https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-setstablepowerstate
+ //
+ // NB! This is a _global_ setting, affecting other processes (and 3D
+ // APIs such as Vulkan), as long as this application is running. Hence
+ // making it an env.var. for now. Never enable it in production. But
+ // extremely useful for the GPU timings with NVIDIA at least; the
+ // timestamps become stable and smooth, making the number readable and
+ // actually useful e.g. in Quick 3D's DebugView when this is enabled.
+ // (otherwise the number's all over the place)
+ //
+ // See also
+ // https://developer.nvidia.com/blog/advanced-api-performance-setstablepowerstate/
+ // for possible other approaches.
+ //
+ if (wantsStablePowerState)
+ dev->SetStablePowerState(TRUE);
+
+ hr = cmdQueue->GetTimestampFrequency(&timestampTicksPerSecond);
+ if (FAILED(hr)) {
+ qWarning("Failed to query timestamp frequency: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return false;
+ }
+ if (!timestampQueryHeap.create(dev, QD3D12_FRAMES_IN_FLIGHT * 2, D3D12_QUERY_HEAP_TYPE_TIMESTAMP)) {
+ qWarning("Failed to create timestamp query pool");
+ return false;
+ }
+ const quint32 readbackBufSize = QD3D12_FRAMES_IN_FLIGHT * 2 * sizeof(quint64);
+ if (!timestampReadbackArea.create(this, readbackBufSize, D3D12_HEAP_TYPE_READBACK)) {
+ qWarning("Failed to create timestamp readback buffer");
+ return false;
+ }
+ timestampReadbackArea.mem.buffer->SetName(L"Timestamp readback buffer");
+ memset(timestampReadbackArea.mem.p, 0, readbackBufSize);
+ }
+
+ caps = {};
+ D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {};
+ if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3)))) {
+ caps.multiView = options3.ViewInstancingTier != D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED;
+ // https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html
+ caps.textureViewFormat = options3.CastingFullyTypedFormatSupported;
+ }
+
deviceLost = false;
offscreenActive = false;
@@ -430,6 +521,9 @@ void QRhiD3D12::destroy()
}
}
+ timestampQueryHeap.destroy();
+ timestampReadbackArea.destroy();
+
shaderVisibleCbvSrvUavHeap.destroy();
for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i)
@@ -445,8 +539,10 @@ void QRhiD3D12::destroy()
cbvSrvUavPool.destroy();
for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
- cmdAllocators[i]->Release();
- cmdAllocators[i] = nullptr;
+ if (cmdAllocators[i]) {
+ cmdAllocators[i]->Release();
+ cmdAllocators[i] = nullptr;
+ }
}
if (fullFenceEvent) {
@@ -565,7 +661,7 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
return false;
#endif
case QRhi::Timestamps:
- return false; // ###
+ return true;
case QRhi::Instancing:
return true;
case QRhi::CustomInstanceStepRate:
@@ -638,6 +734,14 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return false; // we generate mipmaps ourselves with compute and this is not implemented
+ case QRhi::MultiView:
+ return caps.multiView;
+ case QRhi::TextureViewFormat:
+ return caps.textureViewFormat;
+ case QRhi::ResolveDepthStencil:
+ // there is no Multisample Resolve support for depth/stencil formats
+ // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats
+ return false;
}
return false;
}
@@ -794,15 +898,18 @@ void QRhiD3D12::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline
}
cbD->cmdList->IASetPrimitiveTopology(psD->topology);
+
+ if (psD->viewInstanceMask)
+ cbD->cmdList->SetViewInstanceMask(psD->viewInstanceMask);
}
}
-void QRhiD3D12::visitUniformBuffer(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::UniformBufferData &d,
- int,
- int binding,
- int dynamicOffsetCount,
- const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+void QD3D12CommandBuffer::visitUniformBuffer(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::UniformBufferData &d,
+ int,
+ int binding,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
{
QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf);
quint32 offset = d.offset;
@@ -815,29 +922,30 @@ void QRhiD3D12::visitUniformBuffer(QD3D12Stage s,
}
}
}
- visitorData.cbufs[s].append({ bufD->handles[currentFrameSlot], offset });
+ QRHI_RES_RHI(QRhiD3D12);
+ visitorData.cbufs[s].append({ bufD->handles[rhiD->currentFrameSlot], offset });
}
-void QRhiD3D12::visitTexture(QD3D12Stage s,
- const QRhiShaderResourceBinding::TextureAndSampler &d,
- int)
+void QD3D12CommandBuffer::visitTexture(QD3D12Stage s,
+ const QRhiShaderResourceBinding::TextureAndSampler &d,
+ int)
{
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex);
visitorData.srvs[s].append(texD->srv);
}
-void QRhiD3D12::visitSampler(QD3D12Stage s,
- const QRhiShaderResourceBinding::TextureAndSampler &d,
- int)
+void QD3D12CommandBuffer::visitSampler(QD3D12Stage s,
+ const QRhiShaderResourceBinding::TextureAndSampler &d,
+ int)
{
QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, d.sampler);
visitorData.samplers[s].append(samplerD->lookupOrCreateShaderVisibleDescriptor());
}
-void QRhiD3D12::visitStorageBuffer(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::StorageBufferData &d,
- QD3D12ShaderResourceVisitor::StorageOp,
- int)
+void QD3D12CommandBuffer::visitStorageBuffer(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::StorageBufferData &d,
+ QD3D12ShaderResourceVisitor::StorageOp,
+ int)
{
QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf);
// SPIRV-Cross generated HLSL uses RWByteAddressBuffer
@@ -850,17 +958,17 @@ void QRhiD3D12::visitStorageBuffer(QD3D12Stage s,
visitorData.uavs[s].append({ bufD->handles[0], uavDesc });
}
-void QRhiD3D12::visitStorageImage(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::StorageImageData &d,
- QD3D12ShaderResourceVisitor::StorageOp,
- int)
+void QD3D12CommandBuffer::visitStorageImage(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::StorageImageData &d,
+ QD3D12ShaderResourceVisitor::StorageOp,
+ int)
{
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex);
const bool isCube = texD->m_flags.testFlag(QRhiTexture::CubeMap);
const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray);
const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
- uavDesc.Format = texD->dxgiFormat;
+ uavDesc.Format = texD->rtFormat;
if (isCube) {
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
uavDesc.Texture2DArray.MipSlice = UINT(d.level);
@@ -899,8 +1007,8 @@ void QRhiD3D12::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, srb);
- for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) {
- const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings[i]);
+ for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) {
+ const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings[i]);
switch (b->type) {
case QRhiShaderResourceBinding::UniformBuffer:
{
@@ -1012,14 +1120,15 @@ void QRhiD3D12::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind
QD3D12ShaderResourceVisitor visitor(srbD, stageData, gfxPsD ? 5 : 1);
+ QD3D12CommandBuffer::VisitorData &visitorData(cbD->visitorData);
visitorData = {};
using namespace std::placeholders;
- visitor.uniformBuffer = std::bind(&QRhiD3D12::visitUniformBuffer, this, _1, _2, _3, _4, dynamicOffsetCount, dynamicOffsets);
- visitor.texture = std::bind(&QRhiD3D12::visitTexture, this, _1, _2, _3);
- visitor.sampler = std::bind(&QRhiD3D12::visitSampler, this, _1, _2, _3);
- visitor.storageBuffer = std::bind(&QRhiD3D12::visitStorageBuffer, this, _1, _2, _3, _4);
- visitor.storageImage = std::bind(&QRhiD3D12::visitStorageImage, this, _1, _2, _3, _4);
+ visitor.uniformBuffer = std::bind(&QD3D12CommandBuffer::visitUniformBuffer, cbD, _1, _2, _3, _4, dynamicOffsetCount, dynamicOffsets);
+ visitor.texture = std::bind(&QD3D12CommandBuffer::visitTexture, cbD, _1, _2, _3);
+ visitor.sampler = std::bind(&QD3D12CommandBuffer::visitSampler, cbD, _1, _2, _3);
+ visitor.storageBuffer = std::bind(&QD3D12CommandBuffer::visitStorageBuffer, cbD, _1, _2, _3, _4);
+ visitor.storageImage = std::bind(&QD3D12CommandBuffer::visitStorageImage, cbD, _1, _2, _3, _4);
visitor.visit();
@@ -1375,8 +1484,24 @@ void QRhiD3D12::endExternal(QRhiCommandBuffer *cb)
double QRhiD3D12::lastCompletedGpuTime(QRhiCommandBuffer *cb)
{
- Q_UNUSED(cb);
- return 0;
+ QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+ return cbD->lastGpuTime;
+}
+
+static void calculateGpuTime(QD3D12CommandBuffer *cbD,
+ int timestampPairStartIndex,
+ const quint8 *readbackBufPtr,
+ quint64 timestampTicksPerSecond)
+{
+ const size_t byteOffset = timestampPairStartIndex * sizeof(quint64);
+ const quint64 *p = reinterpret_cast<const quint64 *>(readbackBufPtr + byteOffset);
+ const quint64 startTime = *p++;
+ const quint64 endTime = *p;
+ if (startTime < endTime) {
+ const quint64 ticks = endTime - startTime;
+ const double timeSec = ticks / double(timestampTicksPerSecond);
+ cbD->lastGpuTime = timeSec;
+ }
}
QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
@@ -1400,7 +1525,7 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
// be in flight anymore). With Qt Quick this situation cannot happen anyway
// by design (one QRhi per window).
for (QD3D12SwapChain *sc : std::as_const(swapchains))
- sc->waitCommandCompletionForFrameSlot(sc->currentFrameSlot);
+ sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's
HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
if (FAILED(hr)) {
@@ -1422,6 +1547,16 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
swapChainD->rtWrapper.d.dsv = swapChainD->ds ? swapChainD->ds->dsv.cpuHandle
: D3D12_CPU_DESCRIPTOR_HANDLE { 0 };
+ if (swapChainD->stereo) {
+ swapChainD->rtWrapperRight.d.rtv[0] = swapChainD->sampleDesc.Count > 1
+ ? swapChainD->msaaRtvs[swapChainD->currentBackBufferIndex].cpuHandle
+ : swapChainD->rtvsRight[swapChainD->currentBackBufferIndex].cpuHandle;
+
+ swapChainD->rtWrapperRight.d.dsv =
+ swapChainD->ds ? swapChainD->ds->dsv.cpuHandle : D3D12_CPU_DESCRIPTOR_HANDLE{ 0 };
+ }
+
+
// Time to release things that are marked for currentFrameSlot since due to
// the wait above we know that the previous commands on the GPU for this
// slot must have finished already.
@@ -1439,6 +1574,20 @@ QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
+ if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
+ // Read the timestamps for the previous frame for this slot. (the
+ // ResolveQuery() should have completed by now due to the wait above)
+ const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+ calculateGpuTime(cbD,
+ timestampPairStartIndex,
+ timestampReadbackArea.mem.p,
+ timestampTicksPerSecond);
+ // Write the start timestamp for this frame for this slot.
+ cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ timestampPairStartIndex);
+ }
+
return QRhi::FrameOpSuccess;
}
@@ -1463,7 +1612,20 @@ QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_PRESENT);
barrierGen.enqueueBufferedTransitionBarriers(cbD);
- ID3D12GraphicsCommandList *cmdList = cbD->cmdList;
+ if (timestampQueryHeap.isValid()) {
+ const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+ cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ timestampPairStartIndex + 1);
+ cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ timestampPairStartIndex,
+ 2,
+ timestampReadbackArea.mem.buffer,
+ timestampPairStartIndex * sizeof(quint64));
+ }
+
+ ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
HRESULT hr = cmdList->Close();
if (FAILED(hr)) {
qWarning("Failed to close command list: %s",
@@ -1481,6 +1643,10 @@ QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
{
presentFlags |= DXGI_PRESENT_ALLOW_TEARING;
}
+ if (!swapChainD->swapChain) {
+ qWarning("Failed to present, no swapchain");
+ return QRhi::FrameOpError;
+ }
HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
qWarning("Device loss detected in Present()");
@@ -1547,6 +1713,12 @@ QRhi::FrameOpResult QRhiD3D12::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
bindShaderVisibleHeaps(cbD);
+ if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
+ cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT);
+ }
+
offscreenActive = true;
*cb = cbD;
@@ -1560,7 +1732,20 @@ QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags)
offscreenActive = false;
QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot];
- ID3D12GraphicsCommandList *cmdList = cbD->cmdList;
+ if (timestampQueryHeap.isValid()) {
+ const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+ cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ timestampPairStartIndex + 1);
+ cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
+ D3D12_QUERY_TYPE_TIMESTAMP,
+ timestampPairStartIndex,
+ 2,
+ timestampReadbackArea.mem.buffer,
+ timestampPairStartIndex * sizeof(quint64));
+ }
+
+ ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
HRESULT hr = cmdList->Close();
if (FAILED(hr)) {
qWarning("Failed to close command list: %s",
@@ -1580,6 +1765,14 @@ QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags)
// previous) frame is safe since we waited for completion above.
finishActiveReadbacks(true);
+ // the timestamp query results should be available too, given the wait
+ if (timestampQueryHeap.isValid()) {
+ calculateGpuTime(cbD,
+ currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT,
+ timestampReadbackArea.mem.p,
+ timestampTicksPerSecond);
+ }
+
return QRhi::FrameOpSuccess;
}
@@ -1601,7 +1794,7 @@ QRhi::FrameOpResult QRhiD3D12::finish()
Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
- ID3D12GraphicsCommandList *cmdList = cbD->cmdList;
+ ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
HRESULT hr = cmdList->Close();
if (FAILED(hr)) {
qWarning("Failed to close command list: %s",
@@ -1786,15 +1979,19 @@ void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
barrierGen.addTransitionBarrier(dstTexD->handle, D3D12_RESOURCE_STATE_RESOLVE_DEST);
barrierGen.enqueueBufferedTransitionBarriers(cbD);
- const UINT srcSubresource = calcSubresource(0, UINT(colorAtt.layer()), 1);
- const UINT dstSubresource = calcSubresource(UINT(colorAtt.resolveLevel()),
- UINT(colorAtt.resolveLayer()),
- dstTexD->mipLevelCount);
- cbD->cmdList->ResolveSubresource(dstRes->resource, dstSubresource,
- srcRes->resource, srcSubresource,
- dstTexD->dxgiFormat);
+ const UINT resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1;
+ for (UINT resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
+ const UINT srcSubresource = calcSubresource(0, UINT(colorAtt.layer()) + resolveIdx, 1);
+ const UINT dstSubresource = calcSubresource(UINT(colorAtt.resolveLevel()),
+ UINT(colorAtt.resolveLayer()) + resolveIdx,
+ dstTexD->mipLevelCount);
+ cbD->cmdList->ResolveSubresource(dstRes->resource, dstSubresource,
+ srcRes->resource, srcSubresource,
+ dstTexD->dxgiFormat);
+ }
}
-
+ if (rtTex->m_desc.depthResolveTexture())
+ qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
}
cbD->recordingPass = QD3D12CommandBuffer::NoPass;
@@ -2041,6 +2238,36 @@ void QD3D12CpuDescriptorPool::release(const QD3D12Descriptor &descriptor, quint3
quint64(descriptor.cpuHandle.ptr));
}
+bool QD3D12QueryHeap::create(ID3D12Device *device,
+ quint32 queryCount,
+ D3D12_QUERY_HEAP_TYPE heapType)
+{
+ capacity = queryCount;
+
+ D3D12_QUERY_HEAP_DESC heapDesc = {};
+ heapDesc.Type = heapType;
+ heapDesc.Count = capacity;
+
+ HRESULT hr = device->CreateQueryHeap(&heapDesc, __uuidof(ID3D12QueryHeap), reinterpret_cast<void **>(&heap));
+ if (FAILED(hr)) {
+ qWarning("Failed to create query heap: %s", qPrintable(QSystemError::windowsComString(hr)));
+ heap = nullptr;
+ capacity = 0;
+ return false;
+ }
+
+ return true;
+}
+
+void QD3D12QueryHeap::destroy()
+{
+ if (heap) {
+ heap->Release();
+ heap = nullptr;
+ }
+ capacity = 0;
+}
+
bool QD3D12StagingArea::create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType)
{
Q_ASSERT(heapType == D3D12_HEAP_TYPE_UPLOAD || heapType == D3D12_HEAP_TYPE_READBACK);
@@ -2381,8 +2608,8 @@ static inline QPair<int, int> mapBinding(int binding, const QShader::NativeResou
void QD3D12ShaderResourceVisitor::visit()
{
- for (int bindingIdx = 0, bindingCount = srb->sortedBindings.count(); bindingIdx != bindingCount; ++bindingIdx) {
- const QRhiShaderResourceBinding &b(srb->sortedBindings[bindingIdx]);
+ for (int bindingIdx = 0, bindingCount = srb->m_bindings.count(); bindingIdx != bindingCount; ++bindingIdx) {
+ const QRhiShaderResourceBinding &b(srb->m_bindings[bindingIdx]);
const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
@@ -2533,6 +2760,7 @@ bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
// b0
rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+ rootParams[0].Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
// t0
descriptorRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
@@ -2879,24 +3107,18 @@ void QRhiD3D12::waitGpu()
}
}
-DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleCount(int sampleCount, DXGI_FORMAT format) const
+DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) 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;
- }
+ const int s = effectiveSampleCount(sampleCount);
if (s > 1) {
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {};
msaaInfo.Format = format;
- msaaInfo.SampleCount = s;
+ msaaInfo.SampleCount = UINT(s);
if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) {
if (msaaInfo.NumQualityLevels > 0) {
desc.Count = UINT(s);
@@ -2910,7 +3132,7 @@ DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleCount(int sampleCount, DXGI_FORMAT fo
return desc;
}
-bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList **cmdList)
+bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList)
{
ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot];
if (!*cmdList) {
@@ -2918,7 +3140,7 @@ bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList **
D3D12_COMMAND_LIST_TYPE_DIRECT,
cmdAlloc,
nullptr,
- __uuidof(ID3D12GraphicsCommandList),
+ __uuidof(ID3D12GraphicsCommandList1),
reinterpret_cast<void **>(cmdList));
if (FAILED(hr)) {
qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr)));
@@ -3076,18 +3298,42 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) {
for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level])) {
- const UINT subresource = calcSubresource(UINT(level), is3D ? 0u : UINT(layer), texD->mipLevelCount);
- D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
- UINT64 totalBytes = 0;
- D3D12_RESOURCE_DESC desc = res->desc;
- if (is3D) {
- desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
- desc.DepthOrArraySize = 1;
+ D3D12_SUBRESOURCE_FOOTPRINT footprint = {};
+ footprint.Format = res->desc.Format;
+ footprint.Depth = 1;
+ quint32 totalBytes = 0;
+
+ const QSize subresSize = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+ : subresDesc.sourceSize();
+ const QPoint srcPos = subresDesc.sourceTopLeft();
+ QPoint dstPos = subresDesc.destinationTopLeft();
+
+ if (!subresDesc.image().isNull()) {
+ const QImage img = subresDesc.image();
+ const int bpl = img.bytesPerLine();
+ footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+ totalBytes = footprint.RowPitch * img.height();
+ } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+ QSize blockDim;
+ quint32 bpl = 0;
+ compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim);
+ footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+ const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
+ totalBytes = footprint.RowPitch * rowCount;
+ } else if (!subresDesc.data().isEmpty()) {
+ quint32 bpl = 0;
+ if (subresDesc.dataStride())
+ bpl = subresDesc.dataStride();
+ else
+ textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
+ footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+ totalBytes = footprint.RowPitch * subresSize.height();
+ } else {
+ qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+ continue;
}
- dev->GetCopyableFootprints(&desc, subresource, 1, 0,
- &layout, nullptr, nullptr, &totalBytes);
- const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(quint32(totalBytes), 1);
+ const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(totalBytes, 1);
QD3D12StagingArea::Allocation stagingAlloc;
if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize)
stagingAlloc = smallStagingAreas[currentFrameSlot].get(allocSize);
@@ -3104,32 +3350,29 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
}
}
- const UINT requiredBytesPerLine = layout.Footprint.RowPitch; // multiple of 256
- const QSize subresSize = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
- : subresDesc.sourceSize();
- const QPoint srcPos = subresDesc.sourceTopLeft();
- QPoint dstPos = subresDesc.destinationTopLeft();
-
D3D12_TEXTURE_COPY_LOCATION dst;
dst.pResource = res->resource;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
- dst.SubresourceIndex = subresource;
+ dst.SubresourceIndex = calcSubresource(UINT(level), is3D ? 0u : UINT(layer), texD->mipLevelCount);
D3D12_TEXTURE_COPY_LOCATION src;
src.pResource = stagingAlloc.buffer;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint.Offset = stagingAlloc.bufferOffset;
- src.PlacedFootprint.Footprint = layout.Footprint;
D3D12_BOX srcBox; // back, right, bottom are exclusive
if (!subresDesc.image().isNull()) {
- QImage img = subresDesc.image();
+ const QImage img = subresDesc.image();
const int bpc = qMax(1, img.depth() / 8);
const int bpl = img.bytesPerLine();
QSize size = subresDesc.sourceSize().isEmpty() ? img.size() : subresDesc.sourceSize();
size.setWidth(qMin(size.width(), img.width() - srcPos.x()));
size.setHeight(qMin(size.height(), img.height() - srcPos.y()));
+
+ footprint.Width = size.width();
+ footprint.Height = size.height();
+
srcBox.left = 0;
srcBox.top = 0;
srcBox.right = UINT(size.width());
@@ -3140,7 +3383,7 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
const uchar *imgPtr = img.constBits();
const quint32 lineBytes = size.width() * bpc;
for (int y = 0, h = size.height(); y < h; ++y) {
- memcpy(stagingAlloc.p + y * requiredBytesPerLine,
+ memcpy(stagingAlloc.p + y * footprint.RowPitch,
imgPtr + srcPos.x() * bpc + (y + srcPos.y()) * bpl,
lineBytes);
}
@@ -3157,15 +3400,19 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
// width and height must be multiples of the block width and height
srcBox.right = aligned(subresSize.width(), blockDim.width());
srcBox.bottom = aligned(subresSize.height(), blockDim.height());
+
srcBox.front = 0;
srcBox.back = 1;
- const quint32 copyBytes = qMin(bpl, requiredBytesPerLine);
+ footprint.Width = aligned(subresSize.width(), blockDim.width());
+ footprint.Height = aligned(subresSize.height(), blockDim.height());
+
+ const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
const QByteArray imgData = subresDesc.data();
const char *imgPtr = imgData.constData();
const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
for (int y = 0; y < rowCount; ++y)
- memcpy(stagingAlloc.p + y * requiredBytesPerLine, imgPtr + y * bpl, copyBytes);
+ memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + y * bpl, copyBytes);
} else if (!subresDesc.data().isEmpty()) {
srcBox.left = 0;
srcBox.top = 0;
@@ -3174,24 +3421,24 @@ void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpd
srcBox.front = 0;
srcBox.back = 1;
+ footprint.Width = subresSize.width();
+ footprint.Height = subresSize.height();
+
quint32 bpl = 0;
if (subresDesc.dataStride())
bpl = subresDesc.dataStride();
else
textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
- const quint32 copyBytes = qMin(bpl, requiredBytesPerLine);
+ const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
const QByteArray data = subresDesc.data();
const char *imgPtr = data.constData();
for (int y = 0, h = subresSize.height(); y < h; ++y)
- memcpy(stagingAlloc.p + y * requiredBytesPerLine, imgPtr + y * bpl, copyBytes);
- } else {
- qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
- if (ownStagingArea.has_value())
- ownStagingArea->destroyWithDeferredRelease(&releaseQueue);
- continue;
+ memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + y * bpl, copyBytes);
}
+ src.PlacedFootprint.Footprint = footprint;
+
cbD->cmdList->CopyTextureRegion(&dst,
UINT(dstPos.x()),
UINT(dstPos.y()),
@@ -3742,7 +3989,7 @@ bool QD3D12RenderBuffer::create()
case QRhiRenderBuffer::Color:
{
dxgiFormat = toD3DTextureFormat(backingFormat(), {});
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
D3D12_RESOURCE_DESC resourceDesc = {};
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resourceDesc.Width = UINT64(m_pixelSize.width());
@@ -3783,7 +4030,7 @@ bool QD3D12RenderBuffer::create()
case QRhiRenderBuffer::DepthStencil:
{
dxgiFormat = DS_FORMAT;
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
D3D12_RESOURCE_DESC resourceDesc = {};
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resourceDesc.Width = UINT64(m_pixelSize.width());
@@ -3936,10 +4183,30 @@ bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
: (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
- QRHI_RES_RHI(QRhiD3D12);
dxgiFormat = toD3DTextureFormat(m_format, m_flags);
+ if (isDepth) {
+ srvFormat = toD3DDepthTextureSRVFormat(m_format);
+ rtFormat = toD3DDepthTextureDSVFormat(m_format);
+ } else {
+ srvFormat = dxgiFormat;
+ rtFormat = dxgiFormat;
+ }
+ if (m_writeViewFormat.format != UnknownFormat) {
+ if (isDepth)
+ rtFormat = toD3DDepthTextureDSVFormat(m_writeViewFormat.format);
+ else
+ rtFormat = toD3DTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags());
+ }
+ if (m_readViewFormat.format != UnknownFormat) {
+ if (isDepth)
+ srvFormat = toD3DDepthTextureSRVFormat(m_readViewFormat.format);
+ else
+ srvFormat = toD3DTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags());
+ }
+
+ QRHI_RES_RHI(QRhiD3D12);
mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, dxgiFormat);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
if (sampleDesc.Count > 1) {
if (isCube) {
qWarning("Cubemap texture cannot be multisample");
@@ -3996,14 +4263,13 @@ bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
bool QD3D12Texture::finishCreate()
{
QRHI_RES_RHI(QRhiD3D12);
- const bool isDepth = isDepthTextureFormat(m_format);
const bool isCube = m_flags.testFlag(CubeMap);
const bool is3D = m_flags.testFlag(ThreeDimensional);
const bool isArray = m_flags.testFlag(TextureArray);
const bool is1D = m_flags.testFlag(OneDimensional);
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
- srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
+ srvDesc.Format = srvFormat;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
if (isCube) {
@@ -4093,7 +4359,7 @@ bool QD3D12Texture::create()
bool needsOptimizedClearValueSpecified = false;
UINT resourceFlags = 0;
- if (m_flags.testFlag(RenderTarget)) {
+ if (m_flags.testFlag(RenderTarget) || sampleDesc.Count > 1) {
if (isDepth)
resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
else
@@ -4362,7 +4628,7 @@ QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDesc
QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
if (texD)
- rpD->colorFormat[rpD->colorAttachmentCount] = texD->dxgiFormat;
+ rpD->colorFormat[rpD->colorAttachmentCount] = texD->rtFormat;
else if (rbD)
rpD->colorFormat[rpD->colorAttachmentCount] = rbD->dxgiFormat;
rpD->colorAttachmentCount += 1;
@@ -4410,19 +4676,21 @@ bool QD3D12TextureRenderTarget::create()
qWarning("Could not look up texture handle for render target");
return false;
}
+ const bool isMultiView = it->multiViewCount() >= 2;
+ UINT layerCount = isMultiView ? UINT(it->multiViewCount()) : 1;
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
- rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
+ rtvDesc.Format = texD->rtFormat;
if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
- rtvDesc.Texture2DArray.ArraySize = 1;
+ rtvDesc.Texture2DArray.ArraySize = layerCount;
} else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1DARRAY;
rtvDesc.Texture1DArray.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture1DArray.FirstArraySlice = UINT(colorAtt.layer());
- rtvDesc.Texture1DArray.ArraySize = 1;
+ rtvDesc.Texture1DArray.ArraySize = layerCount;
} else {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1D;
rtvDesc.Texture1D.MipSlice = UINT(colorAtt.level());
@@ -4431,18 +4699,18 @@ bool QD3D12TextureRenderTarget::create()
if (texD->sampleDesc.Count > 1) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer());
- rtvDesc.Texture2DMSArray.ArraySize = 1;
+ rtvDesc.Texture2DMSArray.ArraySize = layerCount;
} else {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
- rtvDesc.Texture2DArray.ArraySize = 1;
+ rtvDesc.Texture2DArray.ArraySize = layerCount;
}
} else if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
rtvDesc.Texture3D.MipSlice = UINT(colorAtt.level());
rtvDesc.Texture3D.FirstWSlice = UINT(colorAtt.layer());
- rtvDesc.Texture3D.WSize = 1;
+ rtvDesc.Texture3D.WSize = layerCount;
} else {
if (texD->sampleDesc.Count > 1) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
@@ -4485,9 +4753,30 @@ bool QD3D12TextureRenderTarget::create()
return false;
}
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
- dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
+ dsvDesc.Format = depthTexD->rtFormat;
dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
: D3D12_DSV_DIMENSION_TEXTURE2D;
+ if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
+ if (depthTexD->sampleDesc.Count > 1) {
+ dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
+ if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+ dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+ dsvDesc.Texture2DMSArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+ } else {
+ dsvDesc.Texture2DMSArray.FirstArraySlice = 0;
+ dsvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+ }
+ } else {
+ dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
+ if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+ dsvDesc.Texture2DArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+ dsvDesc.Texture2DArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+ } else {
+ dsvDesc.Texture2DArray.FirstArraySlice = 0;
+ dsvDesc.Texture2DArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+ }
+ }
+ }
dsv = rhiD->dsvPool.allocate(1);
if (!dsv.isValid()) {
qWarning("Failed to allocate DSV for texture render target");
@@ -4554,8 +4843,6 @@ QD3D12ShaderResourceBindings::~QD3D12ShaderResourceBindings()
void QD3D12ShaderResourceBindings::destroy()
{
- sortedBindings.clear();
-
QRHI_RES_RHI(QRhiD3D12);
if (rhiD)
rhiD->unregisterResource(this);
@@ -4563,20 +4850,14 @@ void QD3D12ShaderResourceBindings::destroy()
bool QD3D12ShaderResourceBindings::create()
{
- if (!sortedBindings.isEmpty())
- destroy();
-
QRHI_RES_RHI(QRhiD3D12);
if (!rhiD->sanityCheckShaderResourceBindings(this))
return false;
rhiD->updateLayoutDesc(this);
- std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
- std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
-
hasDynamicOffset = false;
- for (const QRhiShaderResourceBinding &b : sortedBindings) {
+ for (const QRhiShaderResourceBinding &b : std::as_const(m_bindings)) {
const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) {
hasDynamicOffset = true;
@@ -4599,11 +4880,7 @@ bool QD3D12ShaderResourceBindings::create()
void QD3D12ShaderResourceBindings::updateResources(UpdateFlags flags)
{
- sortedBindings.clear();
- std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
- if (!flags.testFlag(BindingsAreSorted))
- std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
-
+ Q_UNUSED(flags);
generation += 1;
}
@@ -4621,6 +4898,7 @@ void QD3D12ShaderResourceBindings::visitUniformBuffer(QD3D12Stage s,
rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
rootParam.ShaderVisibility = qd3d12_stageToVisibility(s);
rootParam.Descriptor.ShaderRegister = shaderRegister;
+ rootParam.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
visitorData.cbParams[s].append(rootParam);
}
@@ -4812,21 +5090,14 @@ QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D1
return QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
}
-// For now we mirror exactly what's done in the D3D11 backend, meaning we use
-// the old shader compiler (so like fxc, not dxc) to generate shader model 5.0
-// output. Some day this should be moved to the new compiler and DXIL.
-
-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;
-}
+// For shader model < 6.0 we do the same as the D3D11 backend: use the old
+// compiler (D3DCompile) to generate DXBC, just as qsb does (when -c is passed)
+// by invoking fxc, not dxc. For SM >= 6.0 we have to use the new compiler and
+// work with DXIL. And that involves IDxcCompiler and needs the presence of
+// dxcompiler.dll and dxil.dll at runtime. Plus there's a chance we have
+// ancient SDK headers when not using MSVC. So this is heavily optional,
+// meaning support for dxc can be disabled both at build time (no dxcapi.h) and
+// at run time (no DLLs).
static inline void makeHlslTargetString(char target[7], const char stage[3], int version)
{
@@ -4841,9 +5112,139 @@ static inline void makeHlslTargetString(char target[7], const char stage[3], int
target[6] = '\0';
}
+enum class HlslCompileFlag
+{
+ WithDebugInfo = 0x01
+};
+
+static QByteArray legacyCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
+{
+ static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile();
+ if (!d3dCompile) {
+ qWarning("Unable to resolve function D3DCompile()");
+ return QByteArray();
+ }
+
+ ID3DBlob *bytecode = nullptr;
+ ID3DBlob *errors = nullptr;
+ UINT d3dCompileFlags = 0;
+ if (flags & int(HlslCompileFlag::WithDebugInfo))
+ d3dCompileFlags |= D3DCOMPILE_DEBUG;
+
+ HRESULT hr = d3dCompile(hlslSource.shader().constData(), SIZE_T(hlslSource.shader().size()),
+ nullptr, nullptr, nullptr,
+ hlslSource.entryPoint().constData(), target, d3dCompileFlags, 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()),
+ int(errors->GetBufferSize()));
+ errors->Release();
+ }
+ return QByteArray();
+ }
+
+ QByteArray result;
+ result.resize(int(bytecode->GetBufferSize()));
+ memcpy(result.data(), bytecode->GetBufferPointer(), size_t(result.size()));
+ bytecode->Release();
+ return result;
+}
+
+#ifdef QRHI_D3D12_HAS_DXC
+
+#ifndef DXC_CP_UTF8
+#define DXC_CP_UTF8 65001
+#endif
+
+#ifndef DXC_ARG_DEBUG
+#define DXC_ARG_DEBUG L"-Zi"
+#endif
+
+static QByteArray dxcCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
+{
+ static std::pair<IDxcCompiler *, IDxcLibrary *> dxc = QRhiD3D::createDxcCompiler();
+ IDxcCompiler *compiler = dxc.first;
+ if (!compiler) {
+ qWarning("Unable to instantiate IDxcCompiler. Likely no dxcompiler.dll and dxil.dll present. "
+ "Use windeployqt or try https://github.com/microsoft/DirectXShaderCompiler/releases");
+ return QByteArray();
+ }
+ IDxcLibrary *library = dxc.second;
+ if (!library)
+ return QByteArray();
+
+ IDxcBlobEncoding *sourceBlob = nullptr;
+ HRESULT hr = library->CreateBlobWithEncodingOnHeapCopy(hlslSource.shader().constData(),
+ UINT32(hlslSource.shader().size()),
+ DXC_CP_UTF8,
+ &sourceBlob);
+ if (FAILED(hr)) {
+ qWarning("Failed to create source blob for dxc: 0x%x (%s)",
+ uint(hr),
+ qPrintable(QSystemError::windowsComString(hr)));
+ return QByteArray();
+ }
+
+ const QString entryPointStr = QString::fromLatin1(hlslSource.entryPoint());
+ const QString targetStr = QString::fromLatin1(target);
+
+ QVarLengthArray<LPCWSTR, 4> argPtrs;
+ QString debugArg;
+ if (flags & int(HlslCompileFlag::WithDebugInfo)) {
+ debugArg = QString::fromUtf16(reinterpret_cast<const char16_t *>(DXC_ARG_DEBUG));
+ argPtrs.append(reinterpret_cast<LPCWSTR>(debugArg.utf16()));
+ }
+
+ IDxcOperationResult *result = nullptr;
+ hr = compiler->Compile(sourceBlob,
+ nullptr,
+ reinterpret_cast<LPCWSTR>(entryPointStr.utf16()),
+ reinterpret_cast<LPCWSTR>(targetStr.utf16()),
+ argPtrs.data(), argPtrs.count(),
+ nullptr, 0,
+ nullptr,
+ &result);
+ sourceBlob->Release();
+ if (SUCCEEDED(hr))
+ result->GetStatus(&hr);
+ if (FAILED(hr)) {
+ qWarning("HLSL shader compilation failed: 0x%x (%s)",
+ uint(hr),
+ qPrintable(QSystemError::windowsComString(hr)));
+ if (result) {
+ IDxcBlobEncoding *errorsBlob = nullptr;
+ if (SUCCEEDED(result->GetErrorBuffer(&errorsBlob))) {
+ if (errorsBlob) {
+ *error = QString::fromUtf8(static_cast<const char *>(errorsBlob->GetBufferPointer()),
+ int(errorsBlob->GetBufferSize()));
+ errorsBlob->Release();
+ }
+ }
+ }
+ return QByteArray();
+ }
+
+ IDxcBlob *bytecode = nullptr;
+ if FAILED(result->GetResult(&bytecode)) {
+ qWarning("No result from IDxcCompiler: 0x%x (%s)",
+ uint(hr),
+ qPrintable(QSystemError::windowsComString(hr)));
+ return QByteArray();
+ }
+
+ QByteArray ba;
+ ba.resize(int(bytecode->GetBufferSize()));
+ memcpy(ba.data(), bytecode->GetBufferPointer(), size_t(ba.size()));
+ bytecode->Release();
+ return ba;
+}
+
+#endif // QRHI_D3D12_HAS_DXC
+
static QByteArray compileHlslShaderSource(const QShader &shader,
QShader::Variant shaderVariant,
- UINT flags,
+ int flags,
QString *error,
QShaderKey *usedShaderKey)
{
@@ -4900,33 +5301,17 @@ static QByteArray compileHlslShaderSource(const QShader &shader,
break;
}
- static const pD3DCompile d3dCompile = resolveD3DCompile();
- if (!d3dCompile) {
- qWarning("Unable to resolve function D3DCompile()");
- return QByteArray();
- }
-
- ID3DBlob *bytecode = nullptr;
- ID3DBlob *errors = nullptr;
- HRESULT hr = d3dCompile(hlslSource.shader().constData(), SIZE_T(hlslSource.shader().size()),
- nullptr, nullptr, nullptr,
- hlslSource.entryPoint().constData(), target, flags, 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()),
- int(errors->GetBufferSize()));
- errors->Release();
- }
- return QByteArray();
+ if (key.sourceVersion().version() >= 60) {
+#ifdef QRHI_D3D12_HAS_DXC
+ return dxcCompile(hlslSource, target, flags, error);
+#else
+ qWarning("Attempted to runtime-compile HLSL source code for shader model >= 6.0 "
+ "but the Qt build has no support for DXC. "
+ "Rebuild Qt with a recent Windows SDK or switch to an MSVC build.");
+#endif
}
- QByteArray result;
- result.resize(int(bytecode->GetBufferSize()));
- memcpy(result.data(), bytecode->GetBufferPointer(), size_t(result.size()));
- bytecode->Release();
-
- return result;
+ return legacyCompile(hlslSource, target, flags, error);
}
static inline UINT8 toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
@@ -5161,6 +5546,22 @@ static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format
return DXGI_FORMAT_R16G16_FLOAT;
case QRhiVertexInputAttribute::Half:
return DXGI_FORMAT_R16_FLOAT;
+ case QRhiVertexInputAttribute::UShort4:
+ // Note: D3D does not support UShort3. Pass through UShort3 as UShort4.
+ case QRhiVertexInputAttribute::UShort3:
+ return DXGI_FORMAT_R16G16B16A16_UINT;
+ case QRhiVertexInputAttribute::UShort2:
+ return DXGI_FORMAT_R16G16_UINT;
+ case QRhiVertexInputAttribute::UShort:
+ return DXGI_FORMAT_R16_UINT;
+ case QRhiVertexInputAttribute::SShort4:
+ // Note: D3D does not support SShort3. Pass through SShort3 as SShort4.
+ case QRhiVertexInputAttribute::SShort3:
+ return DXGI_FORMAT_R16G16B16A16_SINT;
+ case QRhiVertexInputAttribute::SShort2:
+ return DXGI_FORMAT_R16G16_SINT;
+ case QRhiVertexInputAttribute::SShort:
+ return DXGI_FORMAT_R16_SINT;
}
Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT);
}
@@ -5216,16 +5617,16 @@ bool QD3D12GraphicsPipeline::create()
} else {
QString error;
QShaderKey shaderKey;
- UINT compileFlags = 0;
+ int compileFlags = 0;
if (m_flags.testFlag(CompileShadersWithDebugInfo))
- compileFlags |= D3DCOMPILE_DEBUG;
+ compileFlags |= int(HlslCompileFlag::WithDebugInfo);
const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(),
shaderStage.shaderVariant(),
compileFlags,
&error,
&shaderKey);
if (bytecode.isEmpty()) {
- qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
+ qWarning("HLSL graphics shader compilation failed: %s", qPrintable(error));
return false;
}
@@ -5253,32 +5654,94 @@ bool QD3D12GraphicsPipeline::create()
}
QD3D12RenderPassDescriptor *rpD = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
- const DXGI_SAMPLE_DESC sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, DXGI_FORMAT(rpD->colorFormat[0]));
+ const DXGI_SAMPLE_DESC sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, DXGI_FORMAT(rpD->colorFormat[0]));
+
+ struct {
+ QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
+ QD3D12PipelineStateSubObject<D3D12_INPUT_LAYOUT_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT> inputLayout;
+ QD3D12PipelineStateSubObject<D3D12_PRIMITIVE_TOPOLOGY_TYPE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY> primitiveTopology;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS> VS;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS> HS;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS> DS;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS> GS;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS> PS;
+ QD3D12PipelineStateSubObject<D3D12_RASTERIZER_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER> rasterizerState;
+ QD3D12PipelineStateSubObject<D3D12_DEPTH_STENCIL_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL> depthStencilState;
+ QD3D12PipelineStateSubObject<D3D12_BLEND_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND> blendState;
+ QD3D12PipelineStateSubObject<D3D12_RT_FORMAT_ARRAY, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS> rtFormats;
+ QD3D12PipelineStateSubObject<DXGI_FORMAT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT> dsFormat;
+ QD3D12PipelineStateSubObject<DXGI_SAMPLE_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC> sampleDesc;
+ QD3D12PipelineStateSubObject<UINT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK> sampleMask;
+ QD3D12PipelineStateSubObject<D3D12_VIEW_INSTANCING_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING> viewInstancingDesc;
+ } stream;
+
+ stream.rootSig.object = rootSig;
+
+ QVarLengthArray<D3D12_INPUT_ELEMENT_DESC, 4> inputDescs;
+ QByteArrayList matrixSliceSemantics;
+ if (!shaderBytecode[VS].isEmpty()) {
+ for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
+ it != itEnd; ++it)
+ {
+ D3D12_INPUT_ELEMENT_DESC desc = {};
+ // The output from SPIRV-Cross uses TEXCOORD<location> as the
+ // semantic, except for matrices that are unrolled into consecutive
+ // vec2/3/4s attributes and need TEXCOORD<location>_ as
+ // SemanticName and row/column index as SemanticIndex.
+ const int matrixSlice = it->matrixSlice();
+ if (matrixSlice < 0) {
+ desc.SemanticName = "TEXCOORD";
+ desc.SemanticIndex = UINT(it->location());
+ } else {
+ QByteArray sem;
+ sem.resize(16);
+ qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
+ matrixSliceSemantics.append(sem);
+ desc.SemanticName = matrixSliceSemantics.last().constData();
+ desc.SemanticIndex = UINT(matrixSlice);
+ }
+ desc.Format = toD3DAttributeFormat(it->format());
+ desc.InputSlot = UINT(it->binding());
+ desc.AlignedByteOffset = it->offset();
+ const QRhiVertexInputBinding *inputBinding = m_vertexInputLayout.bindingAt(it->binding());
+ if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance) {
+ desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
+ desc.InstanceDataStepRate = inputBinding->instanceStepRate();
+ } else {
+ desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
+ }
+ inputDescs.append(desc);
+ }
+ }
+
+ stream.inputLayout.object.NumElements = inputDescs.count();
+ stream.inputLayout.object.pInputElementDescs = inputDescs.isEmpty() ? nullptr : inputDescs.constData();
+
+ stream.primitiveTopology.object = toD3DTopologyType(m_topology);
+ topology = toD3DTopology(m_topology, m_patchControlPointCount);
- D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
- psoDesc.pRootSignature = rootSig;
for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
const int d3dStage = qd3d12_stage(shaderStage.type());
switch (d3dStage) {
case VS:
- psoDesc.VS.pShaderBytecode = shaderBytecode[d3dStage].constData();
- psoDesc.VS.BytecodeLength = shaderBytecode[d3dStage].size();
+ stream.VS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+ stream.VS.object.BytecodeLength = shaderBytecode[d3dStage].size();
break;
case HS:
- psoDesc.HS.pShaderBytecode = shaderBytecode[d3dStage].constData();
- psoDesc.HS.BytecodeLength = shaderBytecode[d3dStage].size();
+ stream.HS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+ stream.HS.object.BytecodeLength = shaderBytecode[d3dStage].size();
break;
case DS:
- psoDesc.DS.pShaderBytecode = shaderBytecode[d3dStage].constData();
- psoDesc.DS.BytecodeLength = shaderBytecode[d3dStage].size();
+ stream.DS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+ stream.DS.object.BytecodeLength = shaderBytecode[d3dStage].size();
break;
case GS:
- psoDesc.GS.pShaderBytecode = shaderBytecode[d3dStage].constData();
- psoDesc.GS.BytecodeLength = shaderBytecode[d3dStage].size();
+ stream.GS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+ stream.GS.object.BytecodeLength = shaderBytecode[d3dStage].size();
break;
case PS:
- psoDesc.PS.pShaderBytecode = shaderBytecode[d3dStage].constData();
- psoDesc.PS.BytecodeLength = shaderBytecode[d3dStage].size();
+ stream.PS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+ stream.PS.object.BytecodeLength = shaderBytecode[d3dStage].size();
break;
default:
Q_UNREACHABLE();
@@ -5286,7 +5749,32 @@ bool QD3D12GraphicsPipeline::create()
}
}
- psoDesc.BlendState.IndependentBlendEnable = m_targetBlends.count() > 1;
+ stream.rasterizerState.object.FillMode = toD3DFillMode(m_polygonMode);
+ stream.rasterizerState.object.CullMode = toD3DCullMode(m_cullMode);
+ stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW;
+ stream.rasterizerState.object.DepthBias = m_depthBias;
+ stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias;
+ stream.rasterizerState.object.DepthClipEnable = TRUE;
+ stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1;
+
+ stream.depthStencilState.object.DepthEnable = m_depthTest;
+ stream.depthStencilState.object.DepthWriteMask = m_depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
+ stream.depthStencilState.object.DepthFunc = toD3DCompareOp(m_depthOp);
+ stream.depthStencilState.object.StencilEnable = m_stencilTest;
+ if (m_stencilTest) {
+ stream.depthStencilState.object.StencilReadMask = UINT8(m_stencilReadMask);
+ stream.depthStencilState.object.StencilWriteMask = UINT8(m_stencilWriteMask);
+ stream.depthStencilState.object.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
+ stream.depthStencilState.object.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
+ stream.depthStencilState.object.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
+ stream.depthStencilState.object.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
+ stream.depthStencilState.object.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
+ stream.depthStencilState.object.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
+ stream.depthStencilState.object.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
+ stream.depthStencilState.object.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
+ }
+
+ stream.blendState.object.IndependentBlendEnable = m_targetBlends.count() > 1;
for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
D3D12_RENDER_TARGET_BLEND_DESC blend = {};
@@ -5298,95 +5786,40 @@ bool QD3D12GraphicsPipeline::create()
blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha, false);
blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
- psoDesc.BlendState.RenderTarget[i] = blend;
+ stream.blendState.object.RenderTarget[i] = blend;
}
if (m_targetBlends.isEmpty()) {
D3D12_RENDER_TARGET_BLEND_DESC blend = {};
blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
- psoDesc.BlendState.RenderTarget[0] = blend;
+ stream.blendState.object.RenderTarget[0] = blend;
}
- psoDesc.SampleMask = 0xFFFFFFFF;
+ stream.rtFormats.object.NumRenderTargets = rpD->colorAttachmentCount;
+ for (int i = 0; i < rpD->colorAttachmentCount; ++i)
+ stream.rtFormats.object.RTFormats[i] = DXGI_FORMAT(rpD->colorFormat[i]);
- psoDesc.RasterizerState.FillMode = toD3DFillMode(m_polygonMode);
- psoDesc.RasterizerState.CullMode = toD3DCullMode(m_cullMode);
- psoDesc.RasterizerState.FrontCounterClockwise = m_frontFace == CCW;
- psoDesc.RasterizerState.DepthBias = m_depthBias;
- psoDesc.RasterizerState.SlopeScaledDepthBias = m_slopeScaledDepthBias;
- psoDesc.RasterizerState.DepthClipEnable = TRUE;
- psoDesc.RasterizerState.MultisampleEnable = sampleDesc.Count > 1;
+ stream.dsFormat.object = rpD->hasDepthStencil ? DXGI_FORMAT(rpD->dsFormat) : DXGI_FORMAT_UNKNOWN;
- psoDesc.DepthStencilState.DepthEnable = m_depthTest;
- psoDesc.DepthStencilState.DepthWriteMask = m_depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
- psoDesc.DepthStencilState.DepthFunc = toD3DCompareOp(m_depthOp);
- psoDesc.DepthStencilState.StencilEnable = m_stencilTest;
- if (m_stencilTest) {
- psoDesc.DepthStencilState.StencilReadMask = UINT8(m_stencilReadMask);
- psoDesc.DepthStencilState.StencilWriteMask = UINT8(m_stencilWriteMask);
- psoDesc.DepthStencilState.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
- psoDesc.DepthStencilState.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
- psoDesc.DepthStencilState.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
- psoDesc.DepthStencilState.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
- psoDesc.DepthStencilState.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
- psoDesc.DepthStencilState.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
- psoDesc.DepthStencilState.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
- psoDesc.DepthStencilState.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
- }
+ stream.sampleDesc.object = sampleDesc;
- QVarLengthArray<D3D12_INPUT_ELEMENT_DESC, 4> inputDescs;
- QByteArrayList matrixSliceSemantics;
- if (!shaderBytecode[VS].isEmpty()) {
- for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
- it != itEnd; ++it)
- {
- D3D12_INPUT_ELEMENT_DESC desc = {};
- // The output from SPIRV-Cross uses TEXCOORD<location> as the
- // semantic, except for matrices that are unrolled into consecutive
- // vec2/3/4s attributes and need TEXCOORD<location>_ as
- // SemanticName and row/column index as SemanticIndex.
- const int matrixSlice = it->matrixSlice();
- if (matrixSlice < 0) {
- desc.SemanticName = "TEXCOORD";
- desc.SemanticIndex = UINT(it->location());
- } else {
- QByteArray sem;
- sem.resize(16);
- qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
- matrixSliceSemantics.append(sem);
- desc.SemanticName = matrixSliceSemantics.last().constData();
- desc.SemanticIndex = UINT(matrixSlice);
- }
- desc.Format = toD3DAttributeFormat(it->format());
- desc.InputSlot = UINT(it->binding());
- desc.AlignedByteOffset = it->offset();
- const QRhiVertexInputBinding *inputBinding = m_vertexInputLayout.bindingAt(it->binding());
- if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance) {
- desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
- desc.InstanceDataStepRate = inputBinding->instanceStepRate();
- } else {
- desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
- }
- inputDescs.append(desc);
+ stream.sampleMask.object = 0xFFFFFFFF;
+
+ viewInstanceMask = 0;
+ const bool isMultiView = m_multiViewCount >= 2;
+ stream.viewInstancingDesc.object.ViewInstanceCount = isMultiView ? m_multiViewCount : 0;
+ QVarLengthArray<D3D12_VIEW_INSTANCE_LOCATION, 4> viewInstanceLocations;
+ if (isMultiView) {
+ for (int i = 0; i < m_multiViewCount; ++i) {
+ viewInstanceMask |= (1 << i);
+ viewInstanceLocations.append({ 0, UINT(i) });
}
+ stream.viewInstancingDesc.object.pViewInstanceLocations = viewInstanceLocations.constData();
}
- if (!inputDescs.isEmpty()) {
- psoDesc.InputLayout.pInputElementDescs = inputDescs.constData();
- psoDesc.InputLayout.NumElements = inputDescs.count();
- }
-
- psoDesc.PrimitiveTopologyType = toD3DTopologyType(m_topology);
- topology = toD3DTopology(m_topology, m_patchControlPointCount);
- psoDesc.NumRenderTargets = rpD->colorAttachmentCount;
- for (int i = 0; i < rpD->colorAttachmentCount; ++i)
- psoDesc.RTVFormats[i] = DXGI_FORMAT(rpD->colorFormat[i]);
- psoDesc.DSVFormat = rpD->hasDepthStencil ? DXGI_FORMAT(rpD->dsFormat) : DXGI_FORMAT_UNKNOWN;
- psoDesc.SampleDesc = sampleDesc;
+ const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
ID3D12PipelineState *pso = nullptr;
- HRESULT hr = rhiD->dev->CreateGraphicsPipelineState(&psoDesc,
- __uuidof(ID3D12PipelineState),
- reinterpret_cast<void **>(&pso));
+ HRESULT hr = rhiD->dev->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), reinterpret_cast<void **>(&pso));
if (FAILED(hr)) {
qWarning("Failed to create graphics pipeline state: %s",
qPrintable(QSystemError::windowsComString(hr)));
@@ -5450,9 +5883,9 @@ bool QD3D12ComputePipeline::create()
} else {
QString error;
QShaderKey shaderKey;
- UINT compileFlags = 0;
+ int compileFlags = 0;
if (m_flags.testFlag(CompileShadersWithDebugInfo))
- compileFlags |= D3DCOMPILE_DEBUG;
+ compileFlags |= int(HlslCompileFlag::WithDebugInfo);
const QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(),
m_shaderStage.shaderVariant(),
compileFlags,
@@ -5485,14 +5918,16 @@ bool QD3D12ComputePipeline::create()
return false;
}
- D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
- psoDesc.pRootSignature = rootSig;
- psoDesc.CS.pShaderBytecode = shaderBytecode.constData();
- psoDesc.CS.BytecodeLength = shaderBytecode.size();
+ struct {
+ QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
+ QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS> CS;
+ } stream;
+ stream.rootSig.object = rootSig;
+ stream.CS.object.pShaderBytecode = shaderBytecode.constData();
+ stream.CS.object.BytecodeLength = shaderBytecode.size();
+ const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
ID3D12PipelineState *pso = nullptr;
- HRESULT hr = rhiD->dev->CreateComputePipelineState(&psoDesc,
- __uuidof(ID3D12PipelineState),
- reinterpret_cast<void **>(&pso));
+ HRESULT hr = rhiD->dev->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), reinterpret_cast<void **>(&pso));
if (FAILED(hr)) {
qWarning("Failed to create compute pipeline state: %s",
qPrintable(QSystemError::windowsComString(hr)));
@@ -5644,6 +6079,7 @@ int QD3D12SwapChainRenderTarget::sampleCount() const
QD3D12SwapChain::QD3D12SwapChain(QRhiImplementation *rhi)
: QRhiSwapChain(rhi),
rtWrapper(rhi, this),
+ rtWrapperRight(rhi, this),
cbWrapper(rhi)
{
}
@@ -5700,6 +6136,8 @@ void QD3D12SwapChain::releaseBuffers()
for (UINT i = 0; i < BUFFER_COUNT; ++i) {
rhiD->resourcePool.remove(colorBuffers[i]);
rhiD->rtvPool.release(rtvs[i], 1);
+ if (stereo)
+ rhiD->rtvPool.release(rtvsRight[i], 1);
if (!msaaBuffers[i].isNull())
rhiD->resourcePool.remove(msaaBuffers[i]);
if (msaaRtvs[i].isValid())
@@ -5734,48 +6172,15 @@ QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget()
return &rtWrapper;
}
-QSize QD3D12SwapChain::surfacePixelSize()
+QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
{
- Q_ASSERT(m_window);
- return m_window->size() * m_window->devicePixelRatio();
+ return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
}
-static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
-{
- bool ok = false;
- QRect wr = w->geometry();
- wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
- const QPoint center = wr.center();
- IDXGIOutput *currentOutput = nullptr;
- IDXGIOutput *output = nullptr;
- for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
- DXGI_OUTPUT_DESC desc;
- output->GetDesc(&desc);
- const RECT r = desc.DesktopCoordinates;
- const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
- if (dr.contains(center)) {
- currentOutput = output;
- break;
- } else {
- output->Release();
- }
- }
- if (currentOutput) {
- ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
- currentOutput->Release();
- }
- return ok;
-}
-
-static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
+QSize QD3D12SwapChain::surfacePixelSize()
{
- bool ok = false;
- IDXGIOutput6 *out6 = nullptr;
- if (output6ForWindow(w, adapter, &out6)) {
- ok = SUCCEEDED(out6->GetDesc1(result));
- out6->Release();
- }
- return ok;
+ Q_ASSERT(m_window);
+ return m_window->size() * m_window->devicePixelRatio();
}
bool QD3D12SwapChain::isFormatSupported(Format f)
@@ -5790,7 +6195,7 @@ bool QD3D12SwapChain::isFormatSupported(Format f)
QRHI_RES_RHI(QRhiD3D12);
DXGI_OUTPUT_DESC1 desc1;
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
}
@@ -5801,14 +6206,16 @@ bool QD3D12SwapChain::isFormatSupported(Format f)
QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
- if (m_format != QRhiSwapChain::SDR && m_window) {
+ // Must use m_window, not window, given this may be called before createOrResize().
+ if (m_window) {
QRHI_RES_RHI(QRhiD3D12);
DXGI_OUTPUT_DESC1 hdrOutputDesc;
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
- info.isHardCodedDefaults = false;
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
+ info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
+ info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
}
}
return info;
@@ -5831,25 +6238,19 @@ QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor()
return rpD;
}
-static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
-static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
-
bool QRhiD3D12::ensureDirectCompositionDevice()
{
if (dcompDevice)
return true;
qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)");
-
- HRESULT hr = DCompositionCreateDevice(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast<void **>(&dcompDevice));
- if (FAILED(hr)) {
- qWarning("Failed to Direct Composition device: %s", qPrintable(QSystemError::windowsComString(hr)));
- return false;
- }
-
- return true;
+ dcompDevice = QRhiD3D::createDirectCompositionDevice();
+ return dcompDevice ? true : false;
}
+static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
+static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+
void QD3D12SwapChain::chooseFormats()
{
colorFormat = DEFAULT_FORMAT;
@@ -5857,7 +6258,7 @@ void QD3D12SwapChain::chooseFormats()
hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
DXGI_OUTPUT_DESC1 hdrOutputDesc;
QRHI_RES_RHI(QRhiD3D12);
- if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
+ if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
// https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
switch (m_format) {
@@ -5882,7 +6283,7 @@ void QD3D12SwapChain::chooseFormats()
"(or Use HDR is Off in the Display Settings), ignoring HDR format request");
}
}
- sampleDesc = rhiD->effectiveSampleCount(m_sampleCount, colorFormat);
+ sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, colorFormat);
}
bool QD3D12SwapChain::createOrResize()
@@ -5907,13 +6308,14 @@ bool QD3D12SwapChain::createOrResize()
HWND hwnd = reinterpret_cast<HWND>(window->winId());
HRESULT hr;
QRHI_RES_RHI(QRhiD3D12);
+ stereo = m_window->format().stereo() && rhiD->dxgiFactory->IsWindowedStereoEnabled();
if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
if (rhiD->ensureDirectCompositionDevice()) {
if (!dcompTarget) {
- hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
+ hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, false, &dcompTarget);
if (FAILED(hr)) {
- qWarning("Failed to create Direct Compsition target for the window: %s",
+ qWarning("Failed to create Direct Composition target for the window: %s",
qPrintable(QSystemError::windowsComString(hr)));
}
}
@@ -5949,6 +6351,7 @@ bool QD3D12SwapChain::createOrResize()
desc.Flags = swapChainFlags;
desc.Scaling = DXGI_SCALING_NONE;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+ desc.Stereo = stereo;
if (dcompVisual) {
// With DirectComposition setting AlphaMode to STRAIGHT fails the
@@ -6003,13 +6406,19 @@ bool QD3D12SwapChain::createOrResize()
qWarning("Failed to set content for Direct Composition visual: %s",
qPrintable(QSystemError::windowsComString(hr)));
}
+ } else {
+ // disable Alt+Enter; not relevant when using DirectComposition
+ rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
}
}
if (FAILED(hr)) {
- qWarning("Failed to create D3D12 swapchain: %s", qPrintable(QSystemError::windowsComString(hr)));
+ qWarning("Failed to create D3D12 swapchain: %s"
+ " (Width=%u Height=%u Format=%u SampleCount=%u BufferCount=%u Scaling=%u SwapEffect=%u Stereo=%u)",
+ qPrintable(QSystemError::windowsComString(hr)),
+ desc.Width, desc.Height, UINT(desc.Format), desc.SampleDesc.Count,
+ desc.BufferCount, UINT(desc.Scaling), UINT(desc.SwapEffect), UINT(desc.Stereo));
return false;
}
- rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
hr = rhiD->dev->CreateFence(0,
@@ -6056,6 +6465,16 @@ bool QD3D12SwapChain::createOrResize()
rtvDesc.Format = srgbAdjustedColorFormat;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvs[i].cpuHandle);
+
+ if (stereo) {
+ rtvsRight[i] = rhiD->rtvPool.allocate(1);
+ D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+ rtvDesc.Format = srgbAdjustedColorFormat;
+ rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
+ rtvDesc.Texture2DArray.ArraySize = 1;
+ rtvDesc.Texture2DArray.FirstArraySlice = 1;
+ rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvsRight[i].cpuHandle);
+ }
}
if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
@@ -6128,6 +6547,15 @@ bool QD3D12SwapChain::createOrResize()
rtD->d.colorAttCount = 1;
rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+ rtWrapperRight.setRenderPassDescriptor(m_renderPassDesc);
+ QD3D12SwapChainRenderTarget *rtDr = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapperRight);
+ rtDr->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
+ rtDr->d.pixelSize = pixelSize;
+ rtDr->d.dpr = float(window->devicePixelRatio());
+ rtDr->d.sampleCount = int(sampleDesc.Count);
+ rtDr->d.colorAttCount = 1;
+ rtDr->d.dsAttCount = m_depthStencil ? 1 : 0;
+
if (needsRegistration) {
rhiD->swapchains.insert(this);
rhiD->registerResource(this);
@@ -6137,3 +6565,5 @@ bool QD3D12SwapChain::createOrResize()
}
QT_END_NAMESPACE
+
+#endif // __ID3D12Device2_INTERFACE_DEFINED__
diff --git a/src/gui/rhi/qrhid3d12_p.h b/src/gui/rhi/qrhid3d12_p.h
index d40046355b..3f9abbb5ac 100644
--- a/src/gui/rhi/qrhid3d12_p.h
+++ b/src/gui/rhi/qrhid3d12_p.h
@@ -16,7 +16,6 @@
//
#include "qrhi_p.h"
-#include "qshaderdescription.h"
#include <QWindow>
#include <QBitArray>
@@ -30,6 +29,15 @@
#include "D3D12MemAlloc.h"
+// ID3D12Device2 and ID3D12GraphicsCommandList1 and types and enums introduced
+// with those are hard requirements now. These should be declared in any
+// moderately recent d3d12.h, but if it is an SDK from before Windows 10
+// version 1703 then these types could be missing. In the absence of other
+// options, handle this by skipping all the code and making QRhi::create() fail
+// in such builds.
+#ifdef __ID3D12Device2_INTERFACE_DEFINED__
+#define QRHI_D3D12_AVAILABLE
+
QT_BEGIN_NAMESPACE
static const int QD3D12_FRAMES_IN_FLIGHT = 2;
@@ -110,6 +118,18 @@ struct QD3D12CpuDescriptorPool
const char *debugName;
};
+struct QD3D12QueryHeap
+{
+ bool isValid() const { return heap && capacity; }
+ bool create(ID3D12Device *device,
+ quint32 queryCount,
+ D3D12_QUERY_HEAP_TYPE heapType);
+ void destroy();
+
+ ID3D12QueryHeap *heap = nullptr;
+ quint32 capacity = 0;
+};
+
struct QD3D12StagingArea
{
static const quint32 ALIGNMENT = D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT; // 512 so good enough both for cb and texdata
@@ -667,6 +687,7 @@ struct QD3D12Buffer : public QRhiBuffer
};
QVarLengthArray<HostWrite, 16> pendingHostWrites[QD3D12_FRAMES_IN_FLIGHT];
friend class QRhiD3D12;
+ friend struct QD3D12CommandBuffer;
};
struct QD3D12RenderBuffer : public QRhiRenderBuffer
@@ -710,10 +731,13 @@ struct QD3D12Texture : public QRhiTexture
QD3D12ObjectHandle handle;
QD3D12Descriptor srv;
DXGI_FORMAT dxgiFormat;
+ DXGI_FORMAT srvFormat;
+ DXGI_FORMAT rtFormat; // RTV/DSV/UAV
uint mipLevelCount;
DXGI_SAMPLE_DESC sampleDesc;
uint generation = 0;
friend class QRhiD3D12;
+ friend struct QD3D12CommandBuffer;
};
struct QD3D12Sampler : public QRhiSampler
@@ -847,9 +871,11 @@ struct QD3D12ShaderResourceBindings : public QRhiShaderResourceBindings
QD3D12ShaderResourceVisitor::StorageOp op,
int shaderRegister);
- QVarLengthArray<QRhiShaderResourceBinding, 8> sortedBindings;
bool hasDynamicOffset = false;
uint generation = 0;
+
+ friend class QRhiD3D12;
+ friend struct QD3D12ShaderResourceVisitor;
};
struct QD3D12GraphicsPipeline : public QRhiGraphicsPipeline
@@ -863,6 +889,7 @@ struct QD3D12GraphicsPipeline : public QRhiGraphicsPipeline
QD3D12ObjectHandle rootSigHandle;
std::array<QD3D12ShaderStageData, 5> stageData;
D3D12_PRIMITIVE_TOPOLOGY topology;
+ UINT viewInstanceMask = 0;
uint generation = 0;
friend class QRhiD3D12;
};
@@ -889,7 +916,7 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer
const QRhiNativeHandles *nativeHandles();
- ID3D12GraphicsCommandList *cmdList = nullptr; // not owned
+ ID3D12GraphicsCommandList1 *cmdList = nullptr; // not owned
QRhiD3D12CommandBufferNativeHandles nativeHandlesStruct;
enum PassType {
@@ -921,9 +948,11 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer
currentVertexOffsets = {};
}
+ // per-frame
PassType recordingPass;
QRhiRenderTarget *currentTarget;
+ // per-pass
QD3D12GraphicsPipeline *currentGraphicsPipeline;
QD3D12ComputePipeline *currentComputePipeline;
uint currentPipelineGeneration;
@@ -935,6 +964,38 @@ struct QD3D12CommandBuffer : public QRhiCommandBuffer
DXGI_FORMAT currentIndexFormat;
std::array<QD3D12ObjectHandle, D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> currentVertexBuffers;
std::array<quint32, D3D12_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> currentVertexOffsets;
+
+ // global
+ double lastGpuTime = 0;
+
+ // per-setShaderResources
+ struct VisitorData {
+ QVarLengthArray<QPair<QD3D12ObjectHandle, quint32>, 4> cbufs[6];
+ QVarLengthArray<QD3D12Descriptor, 8> srvs[6];
+ QVarLengthArray<QD3D12Descriptor, 8> samplers[6];
+ QVarLengthArray<QPair<QD3D12ObjectHandle, D3D12_UNORDERED_ACCESS_VIEW_DESC>, 4> uavs[6];
+ } visitorData;
+
+ void visitUniformBuffer(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::UniformBufferData &d,
+ int shaderRegister,
+ int binding,
+ int dynamicOffsetCount,
+ const QRhiCommandBuffer::DynamicOffset *dynamicOffsets);
+ void visitTexture(QD3D12Stage s,
+ const QRhiShaderResourceBinding::TextureAndSampler &d,
+ int shaderRegister);
+ void visitSampler(QD3D12Stage s,
+ const QRhiShaderResourceBinding::TextureAndSampler &d,
+ int shaderRegister);
+ void visitStorageBuffer(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::StorageBufferData &d,
+ QD3D12ShaderResourceVisitor::StorageOp op,
+ int shaderRegister);
+ void visitStorageImage(QD3D12Stage s,
+ const QRhiShaderResourceBinding::Data::StorageImageData &d,
+ QD3D12ShaderResourceVisitor::StorageOp op,
+ int shaderRegister);
};
struct QD3D12SwapChain : public QRhiSwapChain
@@ -945,6 +1006,7 @@ struct QD3D12SwapChain : public QRhiSwapChain
QRhiCommandBuffer *currentFrameCommandBuffer() override;
QRhiRenderTarget *currentFrameRenderTarget() override;
+ QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
QSize surfacePixelSize() override;
bool isFormatSupported(Format f) override;
@@ -964,6 +1026,7 @@ struct QD3D12SwapChain : public QRhiSwapChain
QSize pixelSize;
UINT swapInterval = 1;
UINT swapChainFlags = 0;
+ BOOL stereo = false;
DXGI_FORMAT colorFormat;
DXGI_FORMAT srgbAdjustedColorFormat;
DXGI_COLOR_SPACE_TYPE hdrColorSpace;
@@ -972,24 +1035,33 @@ struct QD3D12SwapChain : public QRhiSwapChain
static const UINT BUFFER_COUNT = 3;
QD3D12ObjectHandle colorBuffers[BUFFER_COUNT];
QD3D12Descriptor rtvs[BUFFER_COUNT];
+ QD3D12Descriptor rtvsRight[BUFFER_COUNT];
DXGI_SAMPLE_DESC sampleDesc;
QD3D12ObjectHandle msaaBuffers[BUFFER_COUNT];
QD3D12Descriptor msaaRtvs[BUFFER_COUNT];
QD3D12RenderBuffer *ds = nullptr;
UINT currentBackBufferIndex = 0;
QD3D12SwapChainRenderTarget rtWrapper;
+ QD3D12SwapChainRenderTarget rtWrapperRight;
QD3D12CommandBuffer cbWrapper;
struct FrameResources {
ID3D12Fence *fence = nullptr;
HANDLE fenceEvent = nullptr;
UINT64 fenceCounter = 0;
- ID3D12GraphicsCommandList *cmdList = nullptr;
+ ID3D12GraphicsCommandList1 *cmdList = nullptr;
} frameRes[QD3D12_FRAMES_IN_FLIGHT];
int currentFrameSlot = 0; // index in frameRes
};
+template<typename T, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE Type>
+struct alignas(void*) QD3D12PipelineStateSubObject
+{
+ D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type = Type;
+ T object = {};
+};
+
class QRhiD3D12 : public QRhiImplementation
{
public:
@@ -1109,9 +1181,9 @@ public:
void setPipelineCacheData(const QByteArray &data) override;
void waitGpu();
- DXGI_SAMPLE_DESC effectiveSampleCount(int sampleCount, DXGI_FORMAT format) const;
+ DXGI_SAMPLE_DESC effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) const;
bool ensureDirectCompositionDevice();
- bool startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList **cmdList);
+ bool startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList);
void enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates);
void finishActiveReadbacks(bool forced = false);
bool ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h,
@@ -1122,7 +1194,7 @@ public:
void bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD);
bool debugLayer = false;
- ID3D12Device *dev = nullptr;
+ ID3D12Device2 *dev = nullptr;
D3D_FEATURE_LEVEL minimumFeatureLevel = D3D_FEATURE_LEVEL(0);
LUID adapterLuid = {};
bool importedDevice = false;
@@ -1152,6 +1224,9 @@ public:
QD3D12MipmapGenerator mipmapGen;
QD3D12StagingArea smallStagingAreas[QD3D12_FRAMES_IN_FLIGHT];
QD3D12ShaderVisibleDescriptorHeap shaderVisibleCbvSrvUavHeap;
+ UINT64 timestampTicksPerSecond = 0;
+ QD3D12QueryHeap timestampQueryHeap;
+ QD3D12StagingArea timestampReadbackArea;
IDCompositionDevice *dcompDevice = nullptr;
QD3D12SwapChain *currentSwapChain = nullptr;
QSet<QD3D12SwapChain *> swapchains;
@@ -1160,35 +1235,14 @@ public:
bool offscreenActive = false;
QD3D12CommandBuffer *offscreenCb[QD3D12_FRAMES_IN_FLIGHT] = {};
- struct VisitorData {
- QVarLengthArray<QPair<QD3D12ObjectHandle, quint32>, 4> cbufs[6];
- QVarLengthArray<QD3D12Descriptor, 8> srvs[6];
- QVarLengthArray<QD3D12Descriptor, 8> samplers[6];
- QVarLengthArray<QPair<QD3D12ObjectHandle, D3D12_UNORDERED_ACCESS_VIEW_DESC>, 4> uavs[6];
- } visitorData;
-
- void visitUniformBuffer(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::UniformBufferData &d,
- int shaderRegister,
- int binding,
- int dynamicOffsetCount,
- const QRhiCommandBuffer::DynamicOffset *dynamicOffsets);
- void visitTexture(QD3D12Stage s,
- const QRhiShaderResourceBinding::TextureAndSampler &d,
- int shaderRegister);
- void visitSampler(QD3D12Stage s,
- const QRhiShaderResourceBinding::TextureAndSampler &d,
- int shaderRegister);
- void visitStorageBuffer(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::StorageBufferData &d,
- QD3D12ShaderResourceVisitor::StorageOp op,
- int shaderRegister);
- void visitStorageImage(QD3D12Stage s,
- const QRhiShaderResourceBinding::Data::StorageImageData &d,
- QD3D12ShaderResourceVisitor::StorageOp op,
- int shaderRegister);
+ struct {
+ bool multiView = false;
+ bool textureViewFormat = false;
+ } caps;
};
QT_END_NAMESPACE
+#endif // __ID3D12Device2_INTERFACE_DEFINED__
+
#endif
diff --git a/src/gui/rhi/qrhid3dhelpers.cpp b/src/gui/rhi/qrhid3dhelpers.cpp
new file mode 100644
index 0000000000..216c358cbe
--- /dev/null
+++ b/src/gui/rhi/qrhid3dhelpers.cpp
@@ -0,0 +1,172 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrhid3dhelpers_p.h"
+#include <QtCore/private/qsystemlibrary_p.h>
+#include <QtCore/private/qsystemerror_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QRhiD3D {
+
+bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
+{
+ bool ok = false;
+ QRect wr = w->geometry();
+ wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
+ const QPoint center = wr.center();
+ IDXGIOutput *currentOutput = nullptr;
+ IDXGIOutput *output = nullptr;
+ for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
+ DXGI_OUTPUT_DESC desc;
+ output->GetDesc(&desc);
+ const RECT r = desc.DesktopCoordinates;
+ const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
+ if (dr.contains(center)) {
+ currentOutput = output;
+ break;
+ } else {
+ output->Release();
+ }
+ }
+ if (currentOutput) {
+ ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
+ currentOutput->Release();
+ }
+ return ok;
+}
+
+bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
+{
+ bool ok = false;
+ IDXGIOutput6 *out6 = nullptr;
+ if (output6ForWindow(w, adapter, &out6)) {
+ ok = SUCCEEDED(out6->GetDesc1(result));
+ out6->Release();
+ }
+ return ok;
+}
+
+float sdrWhiteLevelInNits(const DXGI_OUTPUT_DESC1 &outputDesc)
+{
+ QVector<DISPLAYCONFIG_PATH_INFO> pathInfos;
+ uint32_t pathInfoCount, modeInfoCount;
+ LONG result;
+ do {
+ if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, &modeInfoCount) == ERROR_SUCCESS) {
+ pathInfos.resize(pathInfoCount);
+ QVector<DISPLAYCONFIG_MODE_INFO> modeInfos(modeInfoCount);
+ result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, pathInfos.data(), &modeInfoCount, modeInfos.data(), nullptr);
+ } else {
+ return 200.0f;
+ }
+ } while (result == ERROR_INSUFFICIENT_BUFFER);
+
+ MONITORINFOEX monitorInfo = {};
+ monitorInfo.cbSize = sizeof(monitorInfo);
+ GetMonitorInfo(outputDesc.Monitor, &monitorInfo);
+
+ for (const DISPLAYCONFIG_PATH_INFO &info : pathInfos) {
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName = {};
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ deviceName.header.size = sizeof(deviceName);
+ deviceName.header.adapterId = info.sourceInfo.adapterId;
+ deviceName.header.id = info.sourceInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
+ if (!wcscmp(monitorInfo.szDevice, deviceName.viewGdiDeviceName)) {
+ DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {};
+ whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
+ whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL);
+ whiteLevel.header.adapterId = info.targetInfo.adapterId;
+ whiteLevel.header.id = info.targetInfo.id;
+ if (DisplayConfigGetDeviceInfo(&whiteLevel.header) == ERROR_SUCCESS)
+ return whiteLevel.SDRWhiteLevel * 80 / 1000.0f;
+ }
+ }
+ }
+
+ return 200.0f;
+}
+
+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);
+ } else {
+ qWarning("Failed to load D3DCompiler_47/43.dll");
+ }
+ }
+ return nullptr;
+}
+
+IDCompositionDevice *createDirectCompositionDevice()
+{
+ QSystemLibrary dcomplib(QStringLiteral("dcomp"));
+ typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)(
+ _In_opt_ IDXGIDevice *dxgiDevice,
+ _In_ REFIID iid,
+ _Outptr_ void **dcompositionDevice);
+ DCompositionCreateDeviceFuncPtr func = reinterpret_cast<DCompositionCreateDeviceFuncPtr>(
+ dcomplib.resolve("DCompositionCreateDevice"));
+ if (!func) {
+ qWarning("Unable to resolve DCompositionCreateDevice, perhaps dcomp.dll is missing?");
+ return nullptr;
+ }
+ IDCompositionDevice *device = nullptr;
+ HRESULT hr = func(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast<void **>(&device));
+ if (FAILED(hr)) {
+ qWarning("Failed to create Direct Composition device: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return nullptr;
+ }
+ return device;
+}
+
+#ifdef QRHI_D3D12_HAS_DXC
+std::pair<IDxcCompiler *, IDxcLibrary *> createDxcCompiler()
+{
+ QSystemLibrary dxclib(QStringLiteral("dxcompiler"));
+ // this will not be in the system library location, hence onlySystemDirectory==false
+ if (!dxclib.load(false)) {
+ qWarning("Failed to load dxcompiler.dll");
+ return {};
+ }
+ DxcCreateInstanceProc func = reinterpret_cast<DxcCreateInstanceProc>(dxclib.resolve("DxcCreateInstance"));
+ if (!func) {
+ qWarning("Unable to resolve DxcCreateInstance");
+ return {};
+ }
+ IDxcCompiler *compiler = nullptr;
+ HRESULT hr = func(CLSID_DxcCompiler, __uuidof(IDxcCompiler), reinterpret_cast<void**>(&compiler));
+ if (FAILED(hr)) {
+ qWarning("Failed to create dxc compiler instance: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return {};
+ }
+ IDxcLibrary *library = nullptr;
+ hr = func(CLSID_DxcLibrary, __uuidof(IDxcLibrary), reinterpret_cast<void**>(&library));
+ if (FAILED(hr)) {
+ qWarning("Failed to create dxc library instance: %s",
+ qPrintable(QSystemError::windowsComString(hr)));
+ return {};
+ }
+ return { compiler, library };
+}
+#endif
+
+void fillDriverInfo(QRhiDriverInfo *info, const DXGI_ADAPTER_DESC1 &desc)
+{
+ const QString name = QString::fromUtf16(reinterpret_cast<const char16_t *>(desc.Description));
+ info->deviceName = name.toUtf8();
+ info->deviceId = desc.DeviceId;
+ info->vendorId = desc.VendorId;
+ info->deviceType = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) ? QRhiDriverInfo::CpuDevice
+ : QRhiDriverInfo::UnknownDevice;
+}
+
+} // namespace
+
+QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhid3dhelpers_p.h b/src/gui/rhi/qrhid3dhelpers_p.h
new file mode 100644
index 0000000000..f31cdc8d11
--- /dev/null
+++ b/src/gui/rhi/qrhid3dhelpers_p.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRHID3DHELPERS_P_H
+#define QRHID3DHELPERS_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 <rhi/qrhi.h>
+
+#include <QtGui/qwindow.h>
+
+#include <dxgi1_6.h>
+#include <dcomp.h>
+#include <d3dcompiler.h>
+
+#if __has_include(<dxcapi.h>)
+#include <dxcapi.h>
+#define QRHI_D3D12_HAS_DXC
+#endif
+
+QT_BEGIN_NAMESPACE
+
+namespace QRhiD3D {
+
+bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result);
+bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result);
+float sdrWhiteLevelInNits(const DXGI_OUTPUT_DESC1 &outputDesc);
+
+pD3DCompile resolveD3DCompile();
+
+IDCompositionDevice *createDirectCompositionDevice();
+
+#ifdef QRHI_D3D12_HAS_DXC
+std::pair<IDxcCompiler *, IDxcLibrary *> createDxcCompiler();
+#endif
+
+void fillDriverInfo(QRhiDriverInfo *info, const DXGI_ADAPTER_DESC1 &desc);
+
+} // namespace
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 774dec310f..dcaa87a5ff 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -7,6 +7,7 @@
#include <QtCore/qmap.h>
#include <QtGui/private/qopenglextensions_p.h>
#include <QtGui/private/qopenglprogrambinarycache_p.h>
+#include <QtGui/private/qwindow_p.h>
#include <qpa/qplatformopenglcontext.h>
#include <qmath.h>
@@ -31,7 +32,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief OpenGL specific initialization parameters.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
An OpenGL-based QRhi needs an already created QSurface that can be used in
@@ -143,7 +144,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Holds the OpenGL context used by the QRhi.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -215,6 +216,10 @@ QT_BEGIN_NAMESPACE
#define GL_DEPTH_COMPONENT32F 0x8CAC
#endif
+#ifndef GL_UNSIGNED_INT_24_8
+#define GL_UNSIGNED_INT_24_8 0x84FA
+#endif
+
#ifndef GL_STENCIL_INDEX
#define GL_STENCIL_INDEX 0x1901
#endif
@@ -240,7 +245,7 @@ QT_BEGIN_NAMESPACE
#endif
#ifndef GL_FRAMEBUFFER_SRGB
-#define GL_FRAMEBUFFER_SRGB 0x8DB9
+#define GL_FRAMEBUFFER_SRGB 0x8DB9
#endif
#ifndef GL_READ_FRAMEBUFFER
@@ -355,6 +360,10 @@ QT_BEGIN_NAMESPACE
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
#endif
+#ifndef GL_TEXTURE_2D_MULTISAMPLE_ARRAY
+#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102
+#endif
+
#ifndef GL_TEXTURE_EXTERNAL_OES
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
@@ -484,21 +493,41 @@ QT_BEGIN_NAMESPACE
#endif
#ifndef GL_TEXTURE_1D
-# define GL_TEXTURE_1D 0x0DE0
+# define GL_TEXTURE_1D 0x0DE0
#endif
#ifndef GL_TEXTURE_1D_ARRAY
-# define GL_TEXTURE_1D_ARRAY 0x8C18
+# define GL_TEXTURE_1D_ARRAY 0x8C18
#endif
#ifndef GL_HALF_FLOAT
-#define GL_HALF_FLOAT 0x140B
+#define GL_HALF_FLOAT 0x140B
#endif
#ifndef GL_MAX_VERTEX_OUTPUT_COMPONENTS
#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122
#endif
+#ifndef GL_TIMESTAMP
+#define GL_TIMESTAMP 0x8E28
+#endif
+
+#ifndef GL_QUERY_RESULT
+#define GL_QUERY_RESULT 0x8866
+#endif
+
+#ifndef GL_QUERY_RESULT_AVAILABLE
+#define GL_QUERY_RESULT_AVAILABLE 0x8867
+#endif
+
+#ifndef GL_BUFFER
+#define GL_BUFFER 0x82E0
+#endif
+
+#ifndef GL_PROGRAM
+#define GL_PROGRAM 0x82E2
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -825,8 +854,8 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.maxDrawBuffers = 1;
caps.hasDrawBuffersFunc = false;
// This does not mean MSAA is not supported, just that we cannot query
- // the supported sample counts.
- caps.maxSamples = 1;
+ // the supported sample counts. Assume that 4x is always supported.
+ caps.maxSamples = 4;
}
caps.msaaRenderBuffer = f->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)
@@ -863,7 +892,13 @@ bool QRhiGles2::create(QRhi::Flags flags)
#else
caps.needsDepthStencilCombinedAttach = false;
#endif
- caps.srgbCapableDefaultFramebuffer = f->hasOpenGLExtension(QOpenGLExtensions::SRGBFrameBuffer);
+
+ // QOpenGLExtensions::SRGBFrameBuffer is not useful here. We need to know if
+ // controlling the sRGB-on-shader-write state is supported, not that if the
+ // default framebuffer is sRGB-capable. And there are two different
+ // extensions for desktop and ES.
+ caps.srgbWriteControl = ctx->hasExtension("GL_EXT_framebuffer_sRGB") || ctx->hasExtension("GL_EXT_sRGB_write_control");
+
caps.coreProfile = actualFormat.profile() == QSurfaceFormat::CoreProfile;
if (caps.gles)
@@ -1009,6 +1044,60 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.halfAttributes = f->hasOpenGLExtension(QOpenGLExtensions::HalfFloatVertex);
+ // We always require GL_OVR_multiview2 for symmetry with other backends.
+ caps.multiView = f->hasOpenGLExtension(QOpenGLExtensions::MultiView)
+ && f->hasOpenGLExtension(QOpenGLExtensions::MultiViewExtended);
+ if (caps.multiView) {
+ glFramebufferTextureMultiviewOVR =
+ reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei)>(
+ ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultiviewOVR")));
+ }
+
+ // Only do timestamp queries on OpenGL 3.3+.
+ caps.timestamps = !caps.gles && (caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 3));
+ if (caps.timestamps) {
+ glQueryCounter = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum)>(
+ ctx->getProcAddress(QByteArrayLiteral("glQueryCounter")));
+ glGetQueryObjectui64v = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLuint, GLenum, quint64 *)>(
+ ctx->getProcAddress(QByteArrayLiteral("glGetQueryObjectui64v")));
+ if (!glQueryCounter || !glGetQueryObjectui64v)
+ caps.timestamps = false;
+ }
+
+ // glObjectLabel is available on OpenGL ES 3.2+ and OpenGL 4.3+
+ if (caps.gles)
+ caps.objectLabel = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2);
+ else
+ caps.objectLabel = caps.ctxMajor > 4 || (caps.ctxMajor == 4 && caps.ctxMinor >= 3);
+ if (caps.objectLabel) {
+ glObjectLabel = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLuint, GLsizei, const GLchar *)>(
+ ctx->getProcAddress(QByteArrayLiteral("glObjectLabel")));
+ }
+
+ if (caps.gles) {
+ // This is the third way to get multisample rendering with GLES. (1. is
+ // multisample render buffer -> resolve to texture; 2. is multisample
+ // texture with GLES 3.1; 3. is this, avoiding the explicit multisample
+ // buffer and should be more efficient with tiled architectures.
+ // Interesting also because 2. does not seem to work in practice on
+ // devices such as the Quest 3)
+ caps.glesMultisampleRenderToTexture = ctx->hasExtension("GL_EXT_multisampled_render_to_texture");
+ if (caps.glesMultisampleRenderToTexture) {
+ glFramebufferTexture2DMultisampleEXT = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei)>(
+ ctx->getProcAddress(QByteArrayLiteral("glFramebufferTexture2DMultisampleEXT")));
+ }
+ caps.glesMultiviewMultisampleRenderToTexture = ctx->hasExtension("GL_OVR_multiview_multisampled_render_to_texture");
+ if (caps.glesMultiviewMultisampleRenderToTexture) {
+ glFramebufferTextureMultisampleMultiviewOVR = reinterpret_cast<void(QOPENGLF_APIENTRYP)(GLenum, GLenum, GLuint, GLint, GLsizei, GLint, GLsizei)>(
+ ctx->getProcAddress(QByteArrayLiteral("glFramebufferTextureMultisampleMultiviewOVR")));
+ }
+ } else {
+ caps.glesMultisampleRenderToTexture = false;
+ caps.glesMultiviewMultisampleRenderToTexture = false;
+ }
+
+ caps.unpackRowLength = !caps.gles || caps.ctxMajor >= 3;
+
nativeHandlesStruct.context = ctx;
contextLost = false;
@@ -1024,6 +1113,11 @@ void QRhiGles2::destroy()
ensureContext();
executeDeferredReleases();
+ if (ofr.tsQueries[0]) {
+ f->glDeleteQueries(2, ofr.tsQueries);
+ ofr.tsQueries[0] = ofr.tsQueries[1] = 0;
+ }
+
if (vao) {
f->glDeleteVertexArrays(1, &vao);
vao = 0;
@@ -1061,6 +1155,7 @@ void QRhiGles2::executeDeferredReleases()
break;
case QRhiGles2::DeferredReleaseEntry::TextureRenderTarget:
f->glDeleteFramebuffers(1, &e.textureRenderTarget.framebuffer);
+ f->glDeleteTextures(1, &e.textureRenderTarget.nonMsaaThrowawayDepthTexture);
break;
default:
Q_UNREACHABLE();
@@ -1080,17 +1175,6 @@ QList<int> QRhiGles2::supportedSampleCounts() const
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;
-}
-
QRhiSwapChain *QRhiGles2::createSwapChain()
{
return new QGles2SwapChain(this);
@@ -1215,13 +1299,13 @@ static inline void toGlTextureFormat(QRhiTexture::Format format, const QRhiGles2
*glintformat = GL_DEPTH_COMPONENT24;
*glsizedintformat = *glintformat;
*glformat = GL_DEPTH_COMPONENT;
- *gltype = GL_UNSIGNED_SHORT;
+ *gltype = GL_UNSIGNED_INT;
break;
case QRhiTexture::D24S8:
*glintformat = GL_DEPTH24_STENCIL8;
*glsizedintformat = *glintformat;
*glformat = GL_DEPTH_STENCIL;
- *gltype = GL_UNSIGNED_SHORT;
+ *gltype = GL_UNSIGNED_INT_24_8;
break;
case QRhiTexture::D32F:
*glintformat = GL_DEPTH_COMPONENT32F;
@@ -1298,7 +1382,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
case QRhi::DebugMarkers:
return false;
case QRhi::Timestamps:
- return false;
+ return caps.timestamps;
case QRhi::Instancing:
return caps.instancing;
case QRhi::CustomInstanceStepRate:
@@ -1344,7 +1428,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
case QRhi::PipelineCacheDataLoadSave:
return caps.programBinary;
case QRhi::ImageDataStride:
- return !caps.gles || caps.ctxMajor >= 3;
+ return caps.unpackRowLength;
case QRhi::RenderBufferImport:
return true;
case QRhi::ThreeDimensionalTextures:
@@ -1371,6 +1455,12 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.texture1D;
case QRhi::ThreeDimensionalTextureMipmaps:
return caps.texture3D;
+ case QRhi::MultiView:
+ return caps.multiView && caps.maxTextureArraySize > 0;
+ case QRhi::TextureViewFormat:
+ return false;
+ case QRhi::ResolveDepthStencil:
+ return true;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -1945,10 +2035,14 @@ const QRhiNativeHandles *QRhiGles2::nativeHandles(QRhiCommandBuffer *cb)
return nullptr;
}
-static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type)
+static inline void addBoundaryCommand(QGles2CommandBuffer *cbD, QGles2CommandBuffer::Command::Cmd type, GLuint tsQuery = 0)
{
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = type;
+ if (type == QGles2CommandBuffer::Command::BeginFrame)
+ cmd.args.beginFrame.timestampQuery = tsQuery;
+ else if (type == QGles2CommandBuffer::Command::EndFrame)
+ cmd.args.endFrame.timestampQuery = tsQuery;
}
void QRhiGles2::beginExternal(QRhiCommandBuffer *cb)
@@ -2010,8 +2104,8 @@ void QRhiGles2::endExternal(QRhiCommandBuffer *cb)
double QRhiGles2::lastCompletedGpuTime(QRhiCommandBuffer *cb)
{
- Q_UNUSED(cb);
- return 0;
+ QGles2CommandBuffer *cbD = QRHI_RES(QGles2CommandBuffer, cb);
+ return cbD->lastGpuTime;
}
QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags)
@@ -2027,7 +2121,17 @@ QRhi::FrameOpResult QRhiGles2::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
executeDeferredReleases();
swapChainD->cb.resetState();
- addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame);
+ if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) {
+ double elapsedSec = 0;
+ if (swapChainD->timestamps.tryQueryTimestamps(swapChainD->currentTimestampPairIndex, this, &elapsedSec))
+ swapChainD->cb.lastGpuTime = elapsedSec;
+ }
+
+ GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
+ GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
+ const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::BeginFrame, recordTimestamps ? tsStart : 0);
return QRhi::FrameOpSuccess;
}
@@ -2037,7 +2141,15 @@ QRhi::FrameOpResult QRhiGles2::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
QGles2SwapChain *swapChainD = QRHI_RES(QGles2SwapChain, swapChain);
Q_ASSERT(currentSwapChain == swapChainD);
- addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame);
+ GLuint tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
+ GLuint tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
+ const bool recordTimestamps = tsStart && tsEnd && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
+ if (recordTimestamps) {
+ swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true;
+ swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QGles2SwapChainTimestamps::TIMESTAMP_PAIRS;
+ }
+
+ addBoundaryCommand(&swapChainD->cb, QGles2CommandBuffer::Command::EndFrame, recordTimestamps ? tsEnd : 0);
if (!ensureContext(swapChainD->surface))
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
@@ -2069,7 +2181,12 @@ QRhi::FrameOpResult QRhiGles2::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
executeDeferredReleases();
ofr.cbWrapper.resetState();
- addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame);
+ if (rhiFlags.testFlag(QRhi::EnableTimestamps) && caps.timestamps) {
+ if (!ofr.tsQueries[0])
+ f->glGenQueries(2, ofr.tsQueries);
+ }
+
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::BeginFrame, ofr.tsQueries[0]);
*cb = &ofr.cbWrapper;
return QRhi::FrameOpSuccess;
@@ -2081,7 +2198,7 @@ QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags)
Q_ASSERT(ofr.active);
ofr.active = false;
- addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame);
+ addBoundaryCommand(&ofr.cbWrapper, QGles2CommandBuffer::Command::EndFrame, ofr.tsQueries[1]);
if (!ensureContext())
return contextLost ? QRhi::FrameOpDeviceLost : QRhi::FrameOpError;
@@ -2094,6 +2211,16 @@ QRhi::FrameOpResult QRhiGles2::endOffscreenFrame(QRhi::EndFrameFlags flags)
// another, sharing context.
f->glFlush();
+ if (ofr.tsQueries[0]) {
+ quint64 timestamps[2];
+ glGetQueryObjectui64v(ofr.tsQueries[1], GL_QUERY_RESULT, &timestamps[1]);
+ glGetQueryObjectui64v(ofr.tsQueries[0], GL_QUERY_RESULT, &timestamps[0]);
+ if (timestamps[1] >= timestamps[0]) {
+ const quint64 nanoseconds = timestamps[1] - timestamps[0];
+ ofr.cbWrapper.lastGpuTime = nanoseconds / 1000000000.0; // seconds
+ }
+ }
+
return QRhi::FrameOpSuccess;
}
@@ -2213,17 +2340,15 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
const GLenum effectiveTarget = faceTargetBase + (isCubeMap ? uint(layer) : 0u);
const QPoint dp = subresDesc.destinationTopLeft();
const QByteArray rawData = subresDesc.data();
- if (!subresDesc.image().isNull()) {
- QImage img = subresDesc.image();
- QSize size = img.size();
+
+ auto setCmdByNotCompressedData = [&](const void* data, QSize size, quint32 dataStride)
+ {
+ quint32 bytesPerLine = 0;
+ quint32 bytesPerPixel = 0;
+ textureFormatInfo(texD->m_format, size, &bytesPerLine, nullptr, &bytesPerPixel);
+
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
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 = effectiveTarget;
@@ -2235,9 +2360,35 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
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.rowLength = 0;
- cmd.args.subImage.data = cbD->retainImage(img);
+
+ if (dataStride == 0)
+ dataStride = bytesPerLine;
+
+ cmd.args.subImage.rowStartAlign = (dataStride & 3) ? 1 : 4;
+ cmd.args.subImage.rowLength = caps.unpackRowLength ? (bytesPerPixel ? dataStride / bytesPerPixel : 0) : 0;
+
+ cmd.args.subImage.data = data;
+ };
+
+ if (!subresDesc.image().isNull()) {
+ QImage img = subresDesc.image();
+ QSize size = img.size();
+ if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+ const QPoint sp = subresDesc.sourceTopLeft();
+ if (!subresDesc.sourceSize().isEmpty())
+ size = subresDesc.sourceSize();
+
+ if (caps.unpackRowLength) {
+ cbD->retainImage(img);
+ // create a non-owning wrapper for the subimage
+ const uchar *data = img.constBits() + sp.y() * img.bytesPerLine() + sp.x() * (qMax(1, img.depth() / 8));
+ img = QImage(data, size.width(), size.height(), img.bytesPerLine(), img.format());
+ } else {
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ }
+ }
+
+ setCmdByNotCompressedData(cbD->retainImage(img), size, img.bytesPerLine());
} else if (!rawData.isEmpty() && isCompressed) {
const int depth = qMax(1, texD->m_depth);
const int arraySize = qMax(0, texD->m_arraySize);
@@ -2305,31 +2456,8 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
} else if (!rawData.isEmpty()) {
const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
: subresDesc.sourceSize();
- quint32 bytesPerLine = 0;
- quint32 bytesPerPixel = 0;
- textureFormatInfo(texD->m_format, size, &bytesPerLine, nullptr, &bytesPerPixel);
- QGles2CommandBuffer::Command &cmd(cbD->commands.get());
- cmd.cmd = QGles2CommandBuffer::Command::SubImage;
- cmd.args.subImage.target = texD->target;
- cmd.args.subImage.texture = texD->texture;
- cmd.args.subImage.faceTarget = effectiveTarget;
- cmd.args.subImage.level = level;
- cmd.args.subImage.dx = dp.x();
- cmd.args.subImage.dy = is1D && isArray ? layer : dp.y();
- cmd.args.subImage.dz = is3D || isArray ? layer : 0;
- 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 alignment
- // requirement) is 4. QImage guarantees 4 byte aligned
- // row starts, but our raw data here does not.
- cmd.args.subImage.rowStartAlign = (bytesPerLine & 3) ? 1 : 4;
- if (subresDesc.dataStride() && bytesPerPixel)
- cmd.args.subImage.rowLength = subresDesc.dataStride() / bytesPerPixel;
- else
- cmd.args.subImage.rowLength = 0;
- cmd.args.subImage.data = cbD->retainData(rawData);
+
+ setCmdByNotCompressedData(cbD->retainData(rawData), size, subresDesc.dataStride());
} else {
qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
}
@@ -2848,6 +2976,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
const QGles2CommandBuffer::Command &cmd(*it);
switch (cmd.cmd) {
case QGles2CommandBuffer::Command::BeginFrame:
+ if (cmd.args.beginFrame.timestampQuery)
+ glQueryCounter(cmd.args.beginFrame.timestampQuery, GL_TIMESTAMP);
if (caps.coreProfile) {
if (!vao)
f->glGenVertexArrays(1, &vao);
@@ -2874,6 +3004,8 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
#endif
if (vao)
f->glBindVertexArray(0);
+ if (cmd.args.endFrame.timestampQuery)
+ glQueryCounter(cmd.args.endFrame.timestampQuery, GL_TIMESTAMP);
break;
case QGles2CommandBuffer::Command::ResetFrame:
if (vao)
@@ -3018,6 +3150,38 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
type = GL_HALF_FLOAT;
size = 1;
break;
+ case QRhiVertexInputAttribute::UShort4:
+ type = GL_UNSIGNED_SHORT;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::UShort3:
+ type = GL_UNSIGNED_SHORT;
+ size = 3;
+ break;
+ case QRhiVertexInputAttribute::UShort2:
+ type = GL_UNSIGNED_SHORT;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::UShort:
+ type = GL_UNSIGNED_SHORT;
+ size = 1;
+ break;
+ case QRhiVertexInputAttribute::SShort4:
+ type = GL_SHORT;
+ size = 4;
+ break;
+ case QRhiVertexInputAttribute::SShort3:
+ type = GL_SHORT;
+ size = 3;
+ break;
+ case QRhiVertexInputAttribute::SShort2:
+ type = GL_SHORT;
+ size = 2;
+ break;
+ case QRhiVertexInputAttribute::SShort:
+ type = GL_SHORT;
+ size = 1;
+ break;
default:
break;
}
@@ -3160,7 +3324,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
}
if (caps.hasDrawBuffersFunc)
f->glDrawBuffers(bufs.count(), bufs.constData());
- if (caps.srgbCapableDefaultFramebuffer) {
+ if (caps.srgbWriteControl) {
if (cmd.args.bindFramebuffer.srgb)
f->glEnable(GL_FRAMEBUFFER_SRGB);
else
@@ -3315,6 +3479,14 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
result->data.resize(w * h * 8);
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_HALF_FLOAT, result->data.data());
break;
+ case QRhiTexture::R16F:
+ result->data.resize(w * h * 2);
+ f->glReadPixels(0, 0, w, h, GL_RED, GL_HALF_FLOAT, result->data.data());
+ break;
+ case QRhiTexture::R32F:
+ result->data.resize(w * h * 4);
+ f->glReadPixels(0, 0, w, h, GL_RED, GL_FLOAT, result->data.data());
+ break;
case QRhiTexture::RGBA32F:
result->data.resize(w * h * 16);
f->glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, result->data.data());
@@ -3420,19 +3592,120 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
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);
+ const bool ds = cmd.args.blitFromRenderbuffer.isDepthStencil;
+ if (ds) {
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ } else {
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ }
+ f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
+ if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_2D_ARRAY) {
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ }
+ } else {
+ if (ds) {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ }
+ }
+ f->glBlitFramebuffer(0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h,
+ 0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h,
+ ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
+ GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
+ f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
+ f->glDeleteFramebuffers(2, fbo);
+ }
+ break;
+ case QGles2CommandBuffer::Command::BlitFromTexture:
+ {
+ // Altering the scissor state, so reset the stored state, although
+ // not strictly required as long as blit is done in endPass() only.
+ cbD->graphicsPassState.reset();
+ f->glDisable(GL_SCISSOR_TEST);
+ GLuint fbo[2];
+ f->glGenFramebuffers(2, fbo);
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
+ const bool ds = cmd.args.blitFromTexture.isDepthStencil;
+ if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) {
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ }
+ } else {
+ if (ds) {
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ }
+ }
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
- if (cmd.args.blitFromRb.target == GL_TEXTURE_3D || cmd.args.blitFromRb.target == GL_TEXTURE_2D_ARRAY) {
- f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel, cmd.args.blitFromRb.dstLayer);
+ if (cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) {
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ }
} else {
- f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRb.target,
- cmd.args.blitFromRb.texture, cmd.args.blitFromRb.dstLevel);
+ if (ds) {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.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,
+ f->glBlitFramebuffer(0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h,
+ 0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h,
+ ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
f->glDeleteFramebuffers(2, fbo);
@@ -3482,6 +3755,13 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
if (caps.compute)
f->glMemoryBarrier(cmd.args.barrier.barriers);
break;
+ case QGles2CommandBuffer::Command::InvalidateFramebuffer:
+ if (caps.gles && caps.ctxMajor >= 3) {
+ f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER,
+ cmd.args.invalidateFramebuffer.attCount,
+ cmd.args.invalidateFramebuffer.att);
+ }
+ break;
default:
break;
}
@@ -4280,32 +4560,129 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
QGles2TextureRenderTarget *rtTex = QRHI_RES(QGles2TextureRenderTarget, cbD->currentTarget);
- if (rtTex->m_desc.cbeginColorAttachments() != rtTex->m_desc.cendColorAttachments()) {
- // handle only 1 color attachment and only (msaa) renderbuffer
- const QRhiColorAttachment &colorAtt(*rtTex->m_desc.cbeginColorAttachments());
- if (colorAtt.resolveTexture()) {
- Q_ASSERT(colorAtt.renderBuffer());
+ for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
+ it != itEnd; ++it)
+ {
+ const QRhiColorAttachment &colorAtt(*it);
+ if (!colorAtt.resolveTexture())
+ continue;
+
+ QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
+ const QSize size = resolveTexD->pixelSize();
+ if (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(cbD->commands.get());
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());
- if (colorTexD->m_flags.testFlag(QRhiTexture::CubeMap))
- cmd.args.blitFromRb.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer());
+ cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer;
+ cmd.args.blitFromRenderbuffer.w = size.width();
+ cmd.args.blitFromRenderbuffer.h = size.height();
+ if (resolveTexD->m_flags.testFlag(QRhiTexture::CubeMap))
+ cmd.args.blitFromRenderbuffer.target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(colorAtt.resolveLayer());
else
- cmd.args.blitFromRb.target = colorTexD->target;
- cmd.args.blitFromRb.texture = colorTexD->texture;
- cmd.args.blitFromRb.dstLevel = colorAtt.resolveLevel();
- const bool hasZ = colorTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
- || colorTexD->m_flags.testFlag(QRhiTexture::TextureArray);
- cmd.args.blitFromRb.dstLayer = hasZ ? colorAtt.resolveLayer() : 0;
+ cmd.args.blitFromRenderbuffer.target = resolveTexD->target;
+ cmd.args.blitFromRenderbuffer.dstTexture = resolveTexD->texture;
+ cmd.args.blitFromRenderbuffer.dstLevel = colorAtt.resolveLevel();
+ const bool hasZ = resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
+ || resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray);
+ cmd.args.blitFromRenderbuffer.dstLayer = hasZ ? colorAtt.resolveLayer() : 0;
+ cmd.args.blitFromRenderbuffer.isDepthStencil = false;
+ } else if (caps.glesMultisampleRenderToTexture) {
+ // Nothing to do, resolving into colorAtt.resolveTexture() is automatic,
+ // colorAtt.texture() is in fact not used for anything.
+ } else {
+ Q_ASSERT(colorAtt.texture());
+ QGles2Texture *texD = QRHI_RES(QGles2Texture, colorAtt.texture());
+ if (texD->pixelSize() != size) {
+ qWarning("Resolve source (%dx%d) and target (%dx%d) size does not match",
+ texD->pixelSize().width(), texD->pixelSize().height(), size.width(), size.height());
+ }
+ const int resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1;
+ for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
+ const int srcLayer = colorAtt.layer() + resolveIdx;
+ const int dstLayer = colorAtt.resolveLayer() + resolveIdx;
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture;
+ if (texD->m_flags.testFlag(QRhiTexture::CubeMap))
+ cmd.args.blitFromTexture.srcTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(srcLayer);
+ else
+ cmd.args.blitFromTexture.srcTarget = texD->target;
+ cmd.args.blitFromTexture.srcTexture = texD->texture;
+ cmd.args.blitFromTexture.srcLevel = colorAtt.level();
+ cmd.args.blitFromTexture.srcLayer = 0;
+ if (texD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || texD->m_flags.testFlag(QRhiTexture::TextureArray))
+ cmd.args.blitFromTexture.srcLayer = srcLayer;
+ cmd.args.blitFromTexture.w = size.width();
+ cmd.args.blitFromTexture.h = size.height();
+ if (resolveTexD->m_flags.testFlag(QRhiTexture::CubeMap))
+ cmd.args.blitFromTexture.dstTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + uint(dstLayer);
+ else
+ cmd.args.blitFromTexture.dstTarget = resolveTexD->target;
+ cmd.args.blitFromTexture.dstTexture = resolveTexD->texture;
+ cmd.args.blitFromTexture.dstLevel = colorAtt.resolveLevel();
+ cmd.args.blitFromTexture.dstLayer = 0;
+ if (resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray))
+ cmd.args.blitFromTexture.dstLayer = dstLayer;
+ cmd.args.blitFromTexture.isDepthStencil = false;
+ }
+ }
+ }
+
+ if (rtTex->m_desc.depthResolveTexture()) {
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthResolveTexture());
+ const QSize size = depthResolveTexD->pixelSize();
+ if (rtTex->m_desc.depthStencilBuffer()) {
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, rtTex->m_desc.depthStencilBuffer());
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
+ cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer;
+ cmd.args.blitFromRenderbuffer.w = size.width();
+ cmd.args.blitFromRenderbuffer.h = size.height();
+ cmd.args.blitFromRenderbuffer.target = depthResolveTexD->target;
+ cmd.args.blitFromRenderbuffer.dstTexture = depthResolveTexD->texture;
+ cmd.args.blitFromRenderbuffer.dstLevel = 0;
+ cmd.args.blitFromRenderbuffer.dstLayer = 0;
+ cmd.args.blitFromRenderbuffer.isDepthStencil = true;
+ } else if (caps.glesMultisampleRenderToTexture) {
+ // Nothing to do, resolving into depthResolveTexture() is automatic.
+ } else {
+ QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture());
+ const int resolveCount = depthTexD->arraySize() >= 2 ? depthTexD->arraySize() : 1;
+ for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture;
+ cmd.args.blitFromTexture.srcTarget = depthTexD->target;
+ cmd.args.blitFromTexture.srcTexture = depthTexD->texture;
+ cmd.args.blitFromTexture.srcLevel = 0;
+ cmd.args.blitFromTexture.srcLayer = resolveIdx;
+ cmd.args.blitFromTexture.w = size.width();
+ cmd.args.blitFromTexture.h = size.height();
+ cmd.args.blitFromTexture.dstTarget = depthResolveTexD->target;
+ cmd.args.blitFromTexture.dstTexture = depthResolveTexD->texture;
+ cmd.args.blitFromTexture.dstLevel = 0;
+ cmd.args.blitFromTexture.dstLayer = resolveIdx;
+ cmd.args.blitFromTexture.isDepthStencil = true;
+ }
+ }
+ }
+
+ const bool mayDiscardDepthStencil =
+ (rtTex->m_desc.depthStencilBuffer()
+ || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::DoNotStoreDepthStencilContents)))
+ && !rtTex->m_desc.depthResolveTexture();
+ if (mayDiscardDepthStencil) {
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer;
+ if (caps.needsDepthStencilCombinedAttach) {
+ cmd.args.invalidateFramebuffer.attCount = 1;
+ cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT;
+ } else {
+ cmd.args.invalidateFramebuffer.attCount = 2;
+ cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_ATTACHMENT;
+ cmd.args.invalidateFramebuffer.att[1] = GL_STENCIL_ATTACHMENT;
}
}
}
@@ -4940,6 +5317,9 @@ bool QGles2Buffer::create()
rhiD->f->glBindBuffer(targetForDataOps, buffer);
rhiD->f->glBufferData(targetForDataOps, nonZeroSize, nullptr, m_type == Dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ if (rhiD->glObjectLabel)
+ rhiD->glObjectLabel(GL_BUFFER, buffer, -1, m_objectName.constData());
+
usageState.access = AccessNone;
rhiD->registerResource(this);
@@ -5094,6 +5474,9 @@ bool QGles2RenderBuffer::create()
break;
}
+ if (rhiD->glObjectLabel)
+ rhiD->glObjectLabel(GL_RENDERBUFFER, renderbuffer, -1, m_objectName.constData());
+
owns = true;
generation += 1;
rhiD->registerResource(this);
@@ -5224,7 +5607,7 @@ bool QGles2Texture::prepareCreate(QSize *adjustedSize)
}
target = isCube ? GL_TEXTURE_CUBE_MAP
- : m_sampleCount > 1 ? GL_TEXTURE_2D_MULTISAMPLE
+ : m_sampleCount > 1 ? (isArray ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_MULTISAMPLE)
: (is3D ? GL_TEXTURE_3D
: (is1D ? (isArray ? GL_TEXTURE_1D_ARRAY : GL_TEXTURE_1D)
: (isArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D)));
@@ -5316,8 +5699,16 @@ bool QGles2Texture::create()
}
}
} else {
- rhiD->f->glTexImage2D(target, 0, GLint(glintformat), size.width(), size.height(),
- 0, glformat, gltype, nullptr);
+ // 2D texture. For multisample textures the GLES 3.1
+ // glStorage2DMultisample must be used for portability.
+ if (m_sampleCount > 1 && rhiD->caps.multisampledTexture) {
+ // internal format must be sized
+ rhiD->f->glTexStorage2DMultisample(target, m_sampleCount, glsizedintformat,
+ size.width(), size.height(), GL_TRUE);
+ } else {
+ rhiD->f->glTexImage2D(target, 0, GLint(glintformat), size.width(), size.height(),
+ 0, glformat, gltype, nullptr);
+ }
}
} else {
// Must be specified with immutable storage functions otherwise
@@ -5328,6 +5719,9 @@ bool QGles2Texture::create()
else if (!is1D && (is3D || isArray))
rhiD->f->glTexStorage3D(target, mipLevelCount, glsizedintformat, size.width(), size.height(),
is3D ? qMax(1, m_depth) : qMax(0, m_arraySize));
+ else if (m_sampleCount > 1)
+ rhiD->f->glTexStorage2DMultisample(target, m_sampleCount, glsizedintformat,
+ size.width(), size.height(), GL_TRUE);
else
rhiD->f->glTexStorage2D(target, mipLevelCount, glsizedintformat, size.width(),
is1D ? qMax(0, m_arraySize) : size.height());
@@ -5340,6 +5734,9 @@ bool QGles2Texture::create()
specified = false;
}
+ if (rhiD->glObjectLabel)
+ rhiD->glObjectLabel(GL_TEXTURE, texture, -1, m_objectName.constData());
+
owns = true;
generation += 1;
@@ -5496,8 +5893,10 @@ void QGles2TextureRenderTarget::destroy()
e.type = QRhiGles2::DeferredReleaseEntry::TextureRenderTarget;
e.textureRenderTarget.framebuffer = framebuffer;
+ e.textureRenderTarget.nonMsaaThrowawayDepthTexture = nonMsaaThrowawayDepthTexture;
framebuffer = 0;
+ nonMsaaThrowawayDepthTexture = 0;
QRHI_RES_RHI(QRhiGles2);
if (rhiD) {
@@ -5544,6 +5943,7 @@ bool QGles2TextureRenderTarget::create()
d.colorAttCount = 0;
int attIndex = 0;
+ int multiViewCount = 0;
for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
d.colorAttCount += 1;
const QRhiColorAttachment &colorAtt(*it);
@@ -5554,20 +5954,56 @@ bool QGles2TextureRenderTarget::create()
QGles2Texture *texD = QRHI_RES(QGles2Texture, texture);
Q_ASSERT(texD->texture && texD->specified);
if (texD->flags().testFlag(QRhiTexture::ThreeDimensional) || texD->flags().testFlag(QRhiTexture::TextureArray)) {
- rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture,
- colorAtt.level(), colorAtt.layer());
+ if (colorAtt.multiViewCount() < 2) {
+ rhiD->f->glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), texD->texture,
+ colorAtt.level(), colorAtt.layer());
+ } else {
+ multiViewCount = colorAtt.multiViewCount();
+ if (texD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture && colorAtt.resolveTexture()) {
+ // Special path for GLES and GL_OVR_multiview_multisampled_render_to_texture:
+ // ignore the color attachment's (multisample) texture
+ // array and give the resolve texture array to GL. (no
+ // explicit resolving is needed by us later on)
+ QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0 + uint(attIndex),
+ resolveTexD->texture,
+ colorAtt.resolveLevel(),
+ texD->sampleCount(),
+ colorAtt.resolveLayer(),
+ multiViewCount);
+ } else {
+ rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0 + uint(attIndex),
+ texD->texture,
+ colorAtt.level(),
+ colorAtt.layer(),
+ multiViewCount);
+ }
+ }
} else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
rhiD->glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex),
texD->target + uint(colorAtt.layer()), texD->texture,
colorAtt.level());
} else {
- const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
- rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()),
- texD->texture, colorAtt.level());
+ if (texD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && colorAtt.resolveTexture()) {
+ // Special path for GLES and GL_EXT_multisampled_render_to_texture:
+ // ignore the color attachment's (multisample) texture and
+ // give the resolve texture to GL. (no explicit resolving is
+ // needed by us later on)
+ QGles2Texture *resolveTexD = QRHI_RES(QGles2Texture, colorAtt.resolveTexture());
+ const GLenum faceTargetBase = resolveTexD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : resolveTexD->target;
+ rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.resolveLayer()),
+ resolveTexD->texture, colorAtt.level(), texD->sampleCount());
+ } else {
+ const GLenum faceTargetBase = texD->flags().testFlag(QRhiTexture::CubeMap) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : texD->target;
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + uint(attIndex), faceTargetBase + uint(colorAtt.layer()),
+ texD->texture, colorAtt.level());
+ }
}
if (attIndex == 0) {
d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
- d.sampleCount = 1;
+ d.sampleCount = texD->sampleCount();
}
} else if (renderBuffer) {
QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, renderBuffer);
@@ -5588,12 +6024,14 @@ bool QGles2TextureRenderTarget::create()
} else {
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
depthRbD->renderbuffer);
- if (depthRbD->stencilRenderbuffer)
+ if (depthRbD->stencilRenderbuffer) {
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthRbD->stencilRenderbuffer);
- else // packed
+ } else {
+ // packed depth-stencil
rhiD->f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
depthRbD->renderbuffer);
+ }
}
if (d.colorAttCount == 0) {
d.pixelSize = depthRbD->pixelSize();
@@ -5601,11 +6039,105 @@ bool QGles2TextureRenderTarget::create()
}
} else {
QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
- rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target,
- depthTexD->texture, 0);
+ if (multiViewCount < 2) {
+ if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && m_desc.depthResolveTexture()) {
+ // Special path for GLES and
+ // GL_EXT_multisampled_render_to_texture, for depth-stencil.
+ // Relevant only when depthResolveTexture is set.
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
+ rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthResolveTexD->target,
+ depthResolveTexD->texture, 0, depthTexD->sampleCount());
+ if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) {
+ rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthResolveTexD->target,
+ depthResolveTexD->texture, 0, depthTexD->sampleCount());
+ }
+ } else {
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target,
+ depthTexD->texture, 0);
+ if (rhiD->isStencilSupportingFormat(depthTexD->format())) {
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->target,
+ depthTexD->texture, 0);
+ }
+ }
+ } else {
+ if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture) {
+ // And so it turns out
+ // https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt
+ // does not work with multisample 2D texture arrays. (at least
+ // that's what Issue 30 in the extension spec seems to imply)
+ //
+ // There is https://registry.khronos.org/OpenGL/extensions/EXT/EXT_multiview_texture_multisample.txt
+ // that seems to resolve that, but that does not seem to
+ // work (or not available) on GLES devices such as the Quest 3.
+ //
+ // So instead, on GLES we can use the
+ // multisample-multiview-auto-resolving version (which in
+ // turn is not supported on desktop GL e.g. by NVIDIA), too
+ // bad we have a multisample depth texture array here as
+ // every other API out there requires that. So, in absence
+ // of a depthResolveTexture, create a temporary one ignoring
+ // what the user has already created.
+ //
+ if (!m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture()) {
+ qWarning("Attempted to create a multiview+multisample QRhiTextureRenderTarget, but DoNotStoreDepthStencilContents was not set."
+ " This path has no choice but to behave as if DoNotStoreDepthStencilContents was set, because QRhi is forced to create"
+ " a throwaway non-multisample depth texture here. Set the flag to silence this warning, or set a depthResolveTexture.");
+ }
+ if (m_desc.depthResolveTexture()) {
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_DEPTH_ATTACHMENT,
+ depthResolveTexD->texture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) {
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_STENCIL_ATTACHMENT,
+ depthResolveTexD->texture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ }
+ } else {
+ if (!nonMsaaThrowawayDepthTexture) {
+ rhiD->f->glGenTextures(1, &nonMsaaThrowawayDepthTexture);
+ rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, nonMsaaThrowawayDepthTexture);
+ rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH24_STENCIL8,
+ depthTexD->pixelSize().width(), depthTexD->pixelSize().height(), multiViewCount);
+ }
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_DEPTH_ATTACHMENT,
+ nonMsaaThrowawayDepthTexture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_STENCIL_ATTACHMENT,
+ nonMsaaThrowawayDepthTexture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ }
+ } else {
+ // The depth texture here must be an array with at least
+ // multiViewCount elements, and the format should be D24 or D32F
+ // for depth only, or D24S8 for depth and stencil.
+ rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->texture,
+ 0, 0, multiViewCount);
+ if (rhiD->isStencilSupportingFormat(depthTexD->format())) {
+ rhiD->glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->texture,
+ 0, 0, multiViewCount);
+ }
+ }
+ }
if (d.colorAttCount == 0) {
d.pixelSize = depthTexD->pixelSize();
- d.sampleCount = 1;
+ d.sampleCount = depthTexD->sampleCount();
}
}
d.dsAttCount = 1;
@@ -5622,6 +6154,9 @@ bool QGles2TextureRenderTarget::create()
return false;
}
+ if (rhiD->glObjectLabel)
+ rhiD->glObjectLabel(GL_FRAMEBUFFER, framebuffer, -1, m_objectName.constData());
+
QRhiRenderTargetAttachmentTracker::updateResIdList<QGles2Texture, QGles2RenderBuffer>(m_desc, &d.currentResIdList);
rhiD->registerResource(this);
@@ -5868,6 +6403,9 @@ bool QGles2GraphicsPipeline::create()
currentSrb = nullptr;
currentSrbGeneration = 0;
+ if (rhiD->glObjectLabel)
+ rhiD->glObjectLabel(GL_PROGRAM, program, -1, m_objectName.constData());
+
rhiD->pipelineCreationEnd();
generation += 1;
rhiD->registerResource(this);
@@ -6038,7 +6576,12 @@ QRhiRenderTarget *QGles2SwapChain::currentFrameRenderTarget(StereoTargetBuffer t
QSize QGles2SwapChain::surfacePixelSize()
{
Q_ASSERT(m_window);
- return m_window->size() * m_window->devicePixelRatio();
+ if (QPlatformWindow *platformWindow = m_window->handle())
+ // Prefer using QPlatformWindow geometry and DPR in order to avoid
+ // errors due to rounded QWindow geometry.
+ return platformWindow->geometry().size() * platformWindow->devicePixelRatio();
+ else
+ return m_window->size() * m_window->devicePixelRatio();
}
bool QGles2SwapChain::isFormatSupported(Format f)
@@ -6095,15 +6638,59 @@ bool QGles2SwapChain::createOrResize()
frameCount = 0;
+ QRHI_RES_RHI(QRhiGles2);
+ if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps) && rhiD->caps.timestamps)
+ timestamps.prepare(rhiD);
+
// The only reason to register this fairly fake gl swapchain
// object with no native resources underneath is to be able to
// implement a safe destroy().
- if (needsRegistration) {
- QRHI_RES_RHI(QRhiGles2);
+ if (needsRegistration)
rhiD->registerResource(this, false);
- }
return true;
}
+void QGles2SwapChainTimestamps::prepare(QRhiGles2 *rhiD)
+{
+ if (!query[0])
+ rhiD->f->glGenQueries(TIMESTAMP_PAIRS * 2, query);
+}
+
+void QGles2SwapChainTimestamps::destroy(QRhiGles2 *rhiD)
+{
+ rhiD->f->glDeleteQueries(TIMESTAMP_PAIRS * 2, query);
+ memset(active, 0, sizeof(active));
+ memset(query, 0, sizeof(query));
+}
+
+bool QGles2SwapChainTimestamps::tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec)
+{
+ if (!active[pairIndex])
+ return false;
+
+ GLuint tsStart = query[pairIndex * 2];
+ GLuint tsEnd = query[pairIndex * 2 + 1];
+
+ GLuint ready = GL_FALSE;
+ rhiD->f->glGetQueryObjectuiv(tsEnd, GL_QUERY_RESULT_AVAILABLE, &ready);
+
+ if (!ready)
+ return false;
+
+ bool result = false;
+ quint64 timestamps[2];
+ rhiD->glGetQueryObjectui64v(tsStart, GL_QUERY_RESULT, &timestamps[0]);
+ rhiD->glGetQueryObjectui64v(tsEnd, GL_QUERY_RESULT, &timestamps[1]);
+
+ if (timestamps[1] >= timestamps[0]) {
+ const quint64 nanoseconds = timestamps[1] - timestamps[0];
+ *elapsedSec = nanoseconds / 1000000000.0;
+ result = true;
+ }
+
+ active[pairIndex] = false;
+ return result;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
index 47d6c3f6d7..4139579864 100644
--- a/src/gui/rhi/qrhigles2_p.h
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -16,7 +16,7 @@
//
#include "qrhi_p.h"
-#include "qshaderdescription.h"
+#include <rhi/qshaderdescription.h>
#include <qopengl.h>
#include <QByteArray>
#include <QWindow>
@@ -27,6 +27,7 @@
QT_BEGIN_NAMESPACE
class QOpenGLExtensions;
+class QRhiGles2;
struct QGles2Buffer : public QRhiBuffer
{
@@ -214,6 +215,7 @@ struct QGles2TextureRenderTarget : public QRhiTextureRenderTarget
QGles2RenderTargetData d;
GLuint framebuffer = 0;
+ GLuint nonMsaaThrowawayDepthTexture = 0;
friend class QRhiGles2;
};
@@ -331,11 +333,13 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
CompressedImage,
CompressedSubImage,
BlitFromRenderbuffer,
+ BlitFromTexture,
GenMip,
BindComputePipeline,
Dispatch,
BarriersForPass,
- Barrier
+ Barrier,
+ InvalidateFramebuffer
};
Cmd cmd;
@@ -343,6 +347,12 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
// QRhiTexture/Buffer/etc. pointers).
union Args {
struct {
+ GLuint timestampQuery;
+ } beginFrame;
+ struct {
+ GLuint timestampQuery;
+ } endFrame;
+ struct {
float x, y, w, h;
float d0, d1;
} viewport;
@@ -494,10 +504,24 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
int w;
int h;
GLenum target;
- GLuint texture;
+ GLuint dstTexture;
int dstLevel;
int dstLayer;
- } blitFromRb;
+ bool isDepthStencil;
+ } blitFromRenderbuffer;
+ struct {
+ GLenum srcTarget;
+ GLuint srcTexture;
+ int srcLevel;
+ int srcLayer;
+ int w;
+ int h;
+ GLenum dstTarget;
+ GLuint dstTexture;
+ int dstLevel;
+ int dstLayer;
+ bool isDepthStencil;
+ } blitFromTexture;
struct {
GLenum target;
GLuint texture;
@@ -516,6 +540,10 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
struct {
GLbitfield barriers;
} barrier;
+ struct {
+ int attCount;
+ GLenum att[3];
+ } invalidateFramebuffer;
} args;
};
@@ -531,6 +559,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
PassType recordingPass;
bool passNeedsResourceTracking;
+ double lastGpuTime = 0;
QRhiRenderTarget *currentTarget;
QRhiGraphicsPipeline *currentGraphicsPipeline;
QRhiComputePipeline *currentComputePipeline;
@@ -626,6 +655,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
void resetState() {
recordingPass = NoPass;
passNeedsResourceTracking = true;
+ // do not zero lastGpuTime
currentTarget = nullptr;
resetCommands();
resetCachedState();
@@ -687,6 +717,18 @@ inline bool operator!=(const QGles2CommandBuffer::GraphicsPassState::Blend &a,
return !(a == b);
}
+struct QGles2SwapChainTimestamps
+{
+ static const int TIMESTAMP_PAIRS = 2;
+
+ bool active[TIMESTAMP_PAIRS] = {};
+ GLuint query[TIMESTAMP_PAIRS * 2] = {};
+
+ void prepare(QRhiGles2 *rhiD);
+ void destroy(QRhiGles2 *rhiD);
+ bool tryQueryTimestamps(int pairIndex, QRhiGles2 *rhiD, double *elapsedSec);
+};
+
struct QGles2SwapChain : public QRhiSwapChain
{
QGles2SwapChain(QRhiImplementation *rhi);
@@ -712,6 +754,8 @@ struct QGles2SwapChain : public QRhiSwapChain
QGles2SwapChainRenderTarget rtRight;
QGles2CommandBuffer cb;
int frameCount = 0;
+ QGles2SwapChainTimestamps timestamps;
+ int currentTimestampPairIndex = 0;
};
class QRhiGles2 : public QRhiImplementation
@@ -854,7 +898,6 @@ public:
QGles2RenderTargetData *enqueueBindFramebuffer(QRhiRenderTarget *rt, QGles2CommandBuffer *cbD,
bool *wantsColorClear = nullptr, bool *wantsDsClear = nullptr);
void enqueueBarriersForPass(QGles2CommandBuffer *cbD);
- int effectiveSampleCount(int sampleCount) const;
QByteArray shaderSource(const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion);
bool compileShader(GLuint program, const QRhiShaderStage &shaderStage, QShaderVersion *shaderVersion);
bool linkProgram(GLuint program);
@@ -909,7 +952,13 @@ public:
GLsizei, const GLvoid *) = nullptr;
void(QOPENGLF_APIENTRYP glFramebufferTexture1D)(GLenum, GLenum, GLenum, GLuint,
GLint) = nullptr;
-
+ void(QOPENGLF_APIENTRYP glFramebufferTextureMultiviewOVR)(GLenum, GLenum, GLuint, GLint,
+ GLint, GLsizei) = nullptr;
+ void (QOPENGLF_APIENTRYP glQueryCounter)(GLuint, GLenum) = nullptr;
+ void (QOPENGLF_APIENTRYP glGetQueryObjectui64v)(GLuint, GLenum, quint64 *) = nullptr;
+ void (QOPENGLF_APIENTRYP glObjectLabel)(GLenum, GLuint, GLsizei, const GLchar *) = nullptr;
+ void (QOPENGLF_APIENTRYP glFramebufferTexture2DMultisampleEXT)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei) = nullptr;
+ void (QOPENGLF_APIENTRYP glFramebufferTextureMultisampleMultiviewOVR)(GLenum, GLenum, GLuint, GLint, GLsizei, GLint, GLsizei) = nullptr;
uint vao = 0;
struct Caps {
Caps()
@@ -941,7 +990,7 @@ public:
depthTexture(false),
packedDepthStencil(false),
needsDepthStencilCombinedAttach(false),
- srgbCapableDefaultFramebuffer(false),
+ srgbWriteControl(false),
coreProfile(false),
uniformBuffers(false),
elementIndexUint(false),
@@ -962,7 +1011,13 @@ public:
geometryShader(false),
texture1D(false),
hasDrawBuffersFunc(false),
- halfAttributes(false)
+ halfAttributes(false),
+ multiView(false),
+ timestamps(false),
+ objectLabel(false),
+ glesMultisampleRenderToTexture(false),
+ glesMultiviewMultisampleRenderToTexture(false),
+ unpackRowLength(false)
{ }
int ctxMajor;
int ctxMinor;
@@ -994,7 +1049,7 @@ public:
uint depthTexture : 1;
uint packedDepthStencil : 1;
uint needsDepthStencilCombinedAttach : 1;
- uint srgbCapableDefaultFramebuffer : 1;
+ uint srgbWriteControl : 1;
uint coreProfile : 1;
uint uniformBuffers : 1;
uint elementIndexUint : 1;
@@ -1016,6 +1071,12 @@ public:
uint texture1D : 1;
uint hasDrawBuffersFunc : 1;
uint halfAttributes : 1;
+ uint multiView : 1;
+ uint timestamps : 1;
+ uint objectLabel : 1;
+ uint glesMultisampleRenderToTexture : 1;
+ uint glesMultiviewMultisampleRenderToTexture : 1;
+ uint unpackRowLength : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QSet<GLint> supportedCompressedFormats;
@@ -1049,6 +1110,7 @@ public:
} renderbuffer;
struct {
GLuint framebuffer;
+ GLuint nonMsaaThrowawayDepthTexture;
} textureRenderTarget;
};
};
@@ -1058,6 +1120,7 @@ public:
OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
bool active = false;
QGles2CommandBuffer cbWrapper;
+ GLuint tsQueries[2] = {};
} ofr;
QHash<QRhiShaderStage, uint> m_shaderCache;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 7ef200649f..9fadfc15fa 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -40,12 +40,18 @@ QT_BEGIN_NAMESPACE
#error ARC not supported
#endif
-// Note: we expect everything here pass the Metal API validation when running
-// in Debug mode in XCode (or with METAL_DEVICE_WRAPPER_TYPE=1). An exception
-// is the nextDrawable Called Early blah blah warning, which is plain and
-// simply false. This may not be present with newer XCode. There may also be
-// warnings about threading (e.g. about accessing view.layer), those are
-// expected for now.
+// Even though the macOS 13 MTLBinaryArchive problem (QTBUG-106703) seems
+// to be solved in later 13.x releases, we have reports from old Intel hardware
+// and older macOS versions where this causes problems (QTBUG-114338).
+// Thus we no longer do OS version based differentiation, but rather have a
+// single toggle that is currently on, and so QRhi::(set)pipelineCache()
+// does nothing with Metal.
+#define QRHI_METAL_DISABLE_BINARY_ARCHIVE
+
+// We should be able to operate with command buffers that do not automatically
+// retain/release the resources used by them. (since we have logic that mirrors
+// other backends such as the Vulkan one anyway)
+#define QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
/*!
\class QRhiMetalInitParams
@@ -53,7 +59,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Metal specific initialization parameters.
- \note This an RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A Metal-based QRhi needs no special parameters for initialization.
@@ -91,16 +97,21 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Holds the Metal device used by the QRhi.
- \note This an RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
/*!
\variable QRhiMetalNativeHandles::dev
+
+ Set to a valid MTLDevice to import an existing device.
*/
/*!
\variable QRhiMetalNativeHandles::cmdQueue
+
+ Set to a valid MTLCommandQueue when importing an existing command queue.
+ When \nullptr, QRhi will create a new command queue.
*/
/*!
@@ -119,7 +130,7 @@ QT_BEGIN_NAMESPACE
between \l{QRhiCommandBuffer::beginPass()} -
\l{QRhiCommandBuffer::endPass()}.
- \note This an RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -158,8 +169,8 @@ struct QRhiMetalData
id<MTLDevice> dev = nil;
id<MTLCommandQueue> cmdQueue = nil;
API_AVAILABLE(macosx(11.0), ios(14.0)) id<MTLBinaryArchive> binArch = nil;
- bool binArchWasEmpty = false;
+ id<MTLCommandBuffer> newCommandBuffer();
MTLRenderPassDescriptor *createDefaultRenderPass(bool hasDepthStencil,
const QColor &colorClearValue,
const QRhiDepthStencilClearValue &depthStencilClearValue,
@@ -357,8 +368,11 @@ struct QMetalRenderTargetData
struct {
ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
id<MTLTexture> dsTex = nil;
+ id<MTLTexture> dsResolveTex = nil;
bool hasStencil = false;
bool depthNeedsStore = false;
+ bool preserveColor = false;
+ bool preserveDs = false;
} fb;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
@@ -377,6 +391,15 @@ struct QMetalGraphicsPipelineData
float slopeScaledDepthBias;
QMetalShader vs;
QMetalShader fs;
+ struct ExtraBufferManager {
+ enum class WorkBufType {
+ DeviceLocal,
+ HostVisible
+ };
+ QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal);
+ QVector<QMetalBuffer *> deviceLocalWorkBuffers;
+ QVector<QMetalBuffer *> hostVisibleWorkBuffers;
+ } extraBufMgr;
struct Tessellation {
QMetalGraphicsPipelineData *q = nullptr;
bool enabled = false;
@@ -410,13 +433,6 @@ struct QMetalGraphicsPipelineData
id<MTLComputePipelineState> vsCompPipeline(QRhiMetal *rhiD, QShader::Variant vertexCompVariant);
id<MTLComputePipelineState> tescCompPipeline(QRhiMetal *rhiD);
id<MTLRenderPipelineState> teseFragRenderPipeline(QRhiMetal *rhiD, QMetalGraphicsPipeline *pipeline);
- enum class WorkBufType {
- DeviceLocal,
- HostVisible
- };
- QMetalBuffer *acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type = WorkBufType::DeviceLocal);
- QVector<QMetalBuffer *> deviceLocalWorkBuffers;
- QVector<QMetalBuffer *> hostVisibleWorkBuffers;
} tess;
void setupVertexInputDescriptor(MTLVertexDescriptor *desc);
void setupStageInputDescriptor(MTLStageInputOutputDescriptor *desc);
@@ -460,7 +476,7 @@ QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *import
importedDevice = importDevice != nullptr;
if (importedDevice) {
- if (d->dev) {
+ if (importDevice->dev) {
d->dev = (id<MTLDevice>) importDevice->dev;
importedCmdQueue = importDevice->cmdQueue != nullptr;
if (importedCmdQueue)
@@ -494,8 +510,24 @@ bool QRhiMetal::probe(QRhiMetalInitParams *params)
return false;
}
+id<MTLCommandBuffer> QRhiMetalData::newCommandBuffer()
+{
+#ifdef QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
+ // 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).
+ return [cmdQueue commandBufferWithUnretainedReferences];
+#else
+ return [cmdQueue commandBuffer];
+#endif
+}
+
bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl)
{
+#ifdef QRHI_METAL_DISABLE_BINARY_ARCHIVE
+ return false;
+#endif
+
if (@available(macOS 11.0, iOS 14.0, *)) {
[binArch release];
MTLBinaryArchiveDescriptor *binArchDesc = [MTLBinaryArchiveDescriptor new];
@@ -508,7 +540,6 @@ bool QRhiMetalData::setupBinaryArchive(NSURL *sourceFileUrl)
qWarning("newBinaryArchiveWithDescriptor failed: %s", qPrintable(msg));
return false;
}
- binArchWasEmpty = sourceFileUrl == nil;
return true;
}
return false;
@@ -536,9 +567,7 @@ bool QRhiMetal::create(QRhi::Flags flags)
// suitable as deviceId because it does not seem stable on macOS and can
// apparently change when the system is rebooted.
-#ifdef Q_OS_IOS
- driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
-#else
+#ifdef Q_OS_MACOS
if (@available(macOS 10.15, *)) {
const MTLDeviceLocation deviceLocation = [d->dev location];
switch (deviceLocation) {
@@ -555,6 +584,8 @@ bool QRhiMetal::create(QRhi::Flags flags)
break;
}
}
+#else
+ driverInfoStruct.deviceType = QRhiDriverInfo::IntegratedDevice;
#endif
const QOperatingSystemVersion ver = QOperatingSystemVersion::current();
@@ -580,6 +611,7 @@ bool QRhiMetal::create(QRhi::Flags flags)
if (@available(macOS 10.15, *))
caps.isAppleGPU = [d->dev supportsFamily:MTLGPUFamilyApple7];
caps.maxThreadGroupSize = 1024;
+ caps.multiView = true;
#elif defined(Q_OS_TVOS)
if ([d->dev supportsFeatureSet: MTLFeatureSet(30003)]) // MTLFeatureSet_tvOS_GPUFamily2_v1
caps.maxTextureSize = 16384;
@@ -606,8 +638,10 @@ bool QRhiMetal::create(QRhi::Flags flags)
}
caps.isAppleGPU = true;
if (@available(iOS 13, *)) {
- if ([d->dev supportsFamily:MTLGPUFamilyApple4])
+ if ([d->dev supportsFamily: MTLGPUFamilyApple4])
caps.maxThreadGroupSize = 1024;
+ if ([d->dev supportsFamily: MTLGPUFamilyApple5])
+ caps.multiView = true;
}
#endif
@@ -657,17 +691,6 @@ QVector<int> QRhiMetal::supportedSampleCounts() const
return caps.supportedSampleCounts;
}
-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);
@@ -833,6 +856,12 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
+ case QRhi::MultiView:
+ return caps.multiView;
+ case QRhi::TextureViewFormat:
+ return false;
+ case QRhi::ResolveDepthStencil:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -1469,11 +1498,11 @@ void QRhiMetal::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline
psD->makeActiveForCurrentRenderPassEncoder(cbD);
} else {
// mark work buffers that can now be safely reused as reusable
- for (QMetalBuffer *workBuf : psD->d->tess.deviceLocalWorkBuffers) {
+ for (QMetalBuffer *workBuf : psD->d->extraBufMgr.deviceLocalWorkBuffers) {
if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
workBuf->lastActiveFrameSlot = -1;
}
- for (QMetalBuffer *workBuf : psD->d->tess.hostVisibleWorkBuffers) {
+ for (QMetalBuffer *workBuf : psD->d->extraBufMgr.hostVisibleWorkBuffers) {
if (workBuf && workBuf->lastActiveFrameSlot == currentFrameSlot)
workBuf->lastActiveFrameSlot = -1;
}
@@ -1965,6 +1994,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
const quint32 vertexOrIndexCount = indexed ? args.drawIndexed.indexCount : args.draw.vertexCount;
QMetalGraphicsPipelineData::Tessellation &tess(graphicsPipeline->d->tess);
+ QMetalGraphicsPipelineData::ExtraBufferManager &extraBufMgr(graphicsPipeline->d->extraBufMgr);
const quint32 patchCount = tess.patchCountForDrawCall(vertexOrIndexCount, instanceCount);
QMetalBuffer *vertOutBuf = nullptr;
QMetalBuffer *tescOutBuf = nullptr;
@@ -1998,7 +2028,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
if (outputBufferBinding >= 0) {
const quint32 workBufSize = tess.vsCompOutputBufferSize(vertexOrIndexCount, instanceCount);
- vertOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ vertOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
if (!vertOutBuf)
return;
[computeEncoder setBuffer: vertOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
@@ -2046,7 +2076,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
if (outputBufferBinding >= 0) {
const quint32 workBufSize = tess.tescCompOutputBufferSize(patchCount);
- tescOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ tescOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
if (!tescOutBuf)
return;
[computeEncoder setBuffer: tescOutBuf->d->buf[0] offset: 0 atIndex: outputBufferBinding];
@@ -2054,14 +2084,14 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
if (patchOutputBufferBinding >= 0) {
const quint32 workBufSize = tess.tescCompPatchOutputBufferSize(patchCount);
- tescPatchOutBuf = tess.acquireWorkBuffer(this, workBufSize);
+ tescPatchOutBuf = extraBufMgr.acquireWorkBuffer(this, workBufSize);
if (!tescPatchOutBuf)
return;
[computeEncoder setBuffer: tescPatchOutBuf->d->buf[0] offset: 0 atIndex: patchOutputBufferBinding];
}
if (tessFactorBufferBinding >= 0) {
- tescFactorBuf = tess.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
+ tescFactorBuf = extraBufMgr.acquireWorkBuffer(this, patchCount * sizeof(MTLQuadTessellationFactorsHalf));
[computeEncoder setBuffer: tescFactorBuf->d->buf[0] offset: 0 atIndex: tessFactorBufferBinding];
}
@@ -2070,7 +2100,7 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
quint32 inControlPointCount;
quint32 patchCount;
} params;
- tescParamsBuf = tess.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::Tessellation::WorkBufType::HostVisible);
+ tescParamsBuf = extraBufMgr.acquireWorkBuffer(this, sizeof(params), QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible);
if (!tescParamsBuf)
return;
params.inControlPointCount = tess.inControlPointCount;
@@ -2139,6 +2169,39 @@ void QRhiMetal::tessellatedDraw(const TessDrawArgs &args)
}
}
+void QRhiMetal::adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb)
+{
+ QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
+ const int multiViewCount = cbD->currentGraphicsPipeline->m_multiViewCount;
+ if (multiViewCount <= 1)
+ return;
+
+ const QMap<int, int> &ebb(cbD->currentGraphicsPipeline->d->vs.nativeShaderInfo.extraBufferBindings);
+ const int viewMaskBufBinding = ebb.value(QShaderPrivate::MslMultiViewMaskBufferBinding, -1);
+ if (viewMaskBufBinding == -1) {
+ qWarning("No extra buffer for multiview in the vertex shader; was it built with --view-count specified?");
+ return;
+ }
+ struct {
+ quint32 viewOffset;
+ quint32 viewCount;
+ } multiViewInfo;
+ multiViewInfo.viewOffset = 0;
+ multiViewInfo.viewCount = quint32(multiViewCount);
+ QMetalBuffer *buf = cbD->currentGraphicsPipeline->d->extraBufMgr.acquireWorkBuffer(this, sizeof(multiViewInfo),
+ QMetalGraphicsPipelineData::ExtraBufferManager::WorkBufType::HostVisible);
+ if (buf) {
+ id<MTLBuffer> mtlbuf = buf->d->buf[0];
+ char *p = reinterpret_cast<char *>([mtlbuf contents]);
+ memcpy(p, &multiViewInfo, sizeof(multiViewInfo));
+ [cbD->d->currentRenderPassEncoder setVertexBuffer: mtlbuf offset: 0 atIndex: viewMaskBufBinding];
+ // The instance count is adjusted for layered rendering. The vertex shader is expected to contain something like:
+ // uint gl_ViewIndex = spvViewMask[0] + (gl_InstanceIndex - gl_BaseInstance) % spvViewMask[1];
+ // where spvViewMask is the buffer with multiViewInfo passed in above.
+ *instanceCount *= multiViewCount;
+ }
+}
+
void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
{
@@ -2157,6 +2220,8 @@ void QRhiMetal::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
return;
}
+ adjustForMultiViewDraw(&instanceCount, cb);
+
if (caps.baseVertexAndInstance) {
[cbD->d->currentRenderPassEncoder drawPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
vertexStart: firstVertex vertexCount: vertexCount instanceCount: instanceCount baseInstance: firstInstance];
@@ -2195,6 +2260,8 @@ void QRhiMetal::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
return;
}
+ adjustForMultiViewDraw(&instanceCount, cb);
+
if (caps.baseVertexAndInstance) {
[cbD->d->currentRenderPassEncoder drawIndexedPrimitives: cbD->currentGraphicsPipeline->d->primitiveType
indexCount: indexCount
@@ -2297,10 +2364,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
[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];
+ swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
QMetalRenderTargetData::ColorAtt colorAtt;
if (swapChainD->samples > 1) {
@@ -2312,6 +2376,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
+ swapChainD->rtWrapper.d->fb.dsResolveTex = nil;
swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
@@ -2337,6 +2402,16 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
}];
+#ifdef QRHI_METAL_COMMAND_BUFFERS_WITH_UNRETAINED_REFERENCES
+ // When Metal API validation diagnostics is enabled in Xcode the texture is
+ // released before the command buffer is done with it. Manually keep it alive
+ // to work around this.
+ id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain];
+ [swapChainD->cbWrapper.d->cb addCompletedHandler:^(id<MTLCommandBuffer>) {
+ [drawableTexture release];
+ }];
+#endif
+
const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
const bool presentsWithTransaction = swapChainD->d->layer.presentsWithTransaction;
if (!presentsWithTransaction && needsPresent) {
@@ -2383,7 +2458,7 @@ QRhi::FrameOpResult QRhiMetal::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi:
d->ofr.active = true;
*cb = &d->ofr.cbWrapper;
- d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
executeDeferredReleases();
d->ofr.cbWrapper.resetState(d->ofr.lastGpuTime);
@@ -2448,10 +2523,10 @@ QRhi::FrameOpResult QRhiMetal::finish()
if (inFrame) {
if (d->ofr.active) {
d->ofr.lastGpuTime += cb.GPUEndTime - cb.GPUStartTime;
- d->ofr.cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ d->ofr.cbWrapper.d->cb = d->newCommandBuffer();
} else {
swapChainD->d->lastGpuTime[currentFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
- swapChainD->cbWrapper.d->cb = [d->cmdQueue commandBufferWithUnretainedReferences];
+ swapChainD->cbWrapper.d->cb = d->newCommandBuffer();
}
}
@@ -2514,7 +2589,6 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
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();
@@ -2523,10 +2597,12 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
w = subresDesc.sourceSize().width();
h = subresDesc.sourceSize().height();
}
- if (img.depth() == 32) {
- memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
- srcOffset = sy * bpl + sx * 4;
- // bpl remains set to the original image's row stride
+ if (w == img.width()) {
+ const int bpc = qMax(1, img.depth() / 8);
+ Q_ASSERT(h * img.bytesPerLine() <= fullImageSizeBytes);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs,
+ img.constBits() + sy * img.bytesPerLine() + sx * bpc,
+ h * img.bytesPerLine());
} else {
img = img.copy(sx, sy, w, h);
bpl = img.bytesPerLine();
@@ -2538,7 +2614,7 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
}
[blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
- sourceOffset: NSUInteger(*curOfs + srcOffset)
+ sourceOffset: NSUInteger(*curOfs)
sourceBytesPerRow: NSUInteger(bpl)
sourceBytesPerImage: 0
sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
@@ -2630,6 +2706,15 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
QMetalCommandBuffer *cbD = QRHI_RES(QMetalCommandBuffer, cb);
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+ id<MTLBlitCommandEncoder> blitEnc = nil;
+ auto ensureBlit = [&blitEnc, cbD, this]() {
+ if (!blitEnc) {
+ blitEnc = [cbD->d->cb blitCommandEncoder];
+ if (debugMarkers)
+ [blitEnc pushDebugGroup: @"Texture upload/copy"];
+ }
+ };
+
for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
@@ -2668,19 +2753,17 @@ void QRhiMetal::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdate
readback.readSize = u.readSize;
readback.result = u.result;
d->activeBufferReadbacks.append(readback);
+#ifdef Q_OS_MACOS
+ if (bufD->d->managed) {
+ // On non-Apple Silicon, manually synchronize memory from GPU to CPU
+ ensureBlit();
+ [blitEnc synchronizeResource:readback.buf];
+ }
+#endif
}
}
}
- id<MTLBlitCommandEncoder> blitEnc = nil;
- auto ensureBlit = [&blitEnc, cbD, this] {
- if (!blitEnc) {
- blitEnc = [cbD->d->cb blitCommandEncoder];
- if (debugMarkers)
- [blitEnc pushDebugGroup: @"Texture upload/copy"];
- }
- };
-
for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
@@ -2891,28 +2974,39 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QMetalTexture, QMetalRenderBuffer>(rtTex->description(), rtD->currentResIdList))
rtTex->create();
cbD->d->currentPassRpDesc = d->createDefaultRenderPass(rtD->dsAttCount, colorClearValue, depthStencilClearValue, rtD->colorAttCount);
- if (rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents)) {
+ if (rtD->fb.preserveColor) {
for (uint i = 0; i < uint(rtD->colorAttCount); ++i)
cbD->d->currentPassRpDesc.colorAttachments[i].loadAction = MTLLoadActionLoad;
}
- if (rtD->dsAttCount && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents)) {
+ if (rtD->dsAttCount && rtD->fb.preserveDs) {
cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
}
+ int colorAttCount = 0;
for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
it != itEnd; ++it)
{
- if (it->texture())
+ colorAttCount += 1;
+ if (it->texture()) {
QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
- else if (it->renderBuffer())
+ if (it->multiViewCount() >= 2)
+ cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(it->multiViewCount());
+ } else if (it->renderBuffer()) {
QRHI_RES(QMetalRenderBuffer, it->renderBuffer())->lastActiveFrameSlot = currentFrameSlot;
+ }
if (it->resolveTexture())
QRHI_RES(QMetalTexture, it->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;
+ if (rtTex->m_desc.depthTexture()) {
+ QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture());
+ depthTexture->lastActiveFrameSlot = currentFrameSlot;
+ if (colorAttCount == 0 && depthTexture->arraySize() >= 2)
+ cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize());
+ }
+ if (rtTex->m_desc.depthResolveTexture())
+ QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
}
break;
default:
@@ -2926,7 +3020,8 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
cbD->d->currentPassRpDesc.colorAttachments[i].depthPlane = NSUInteger(rtD->fb.colorAtt[i].slice);
cbD->d->currentPassRpDesc.colorAttachments[i].level = NSUInteger(rtD->fb.colorAtt[i].level);
if (rtD->fb.colorAtt[i].resolveTex) {
- cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.colorAttachments[i].storeAction = rtD->fb.preserveColor ? MTLStoreActionStoreAndMultisampleResolve
+ : MTLStoreActionMultisampleResolve;
cbD->d->currentPassRpDesc.colorAttachments[i].resolveTexture = rtD->fb.colorAtt[i].resolveTex;
cbD->d->currentPassRpDesc.colorAttachments[i].resolveSlice = NSUInteger(rtD->fb.colorAtt[i].resolveLayer);
cbD->d->currentPassRpDesc.colorAttachments[i].resolveLevel = NSUInteger(rtD->fb.colorAtt[i].resolveLevel);
@@ -2939,6 +3034,15 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
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;
+ if (rtD->fb.dsResolveTex) {
+ cbD->d->currentPassRpDesc.depthAttachment.storeAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve
+ : MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex;
+ if (rtD->fb.hasStencil) {
+ cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex;
+ cbD->d->currentPassRpDesc.stencilAttachment.storeAction = cbD->d->currentPassRpDesc.depthAttachment.storeAction;
+ }
+ }
}
cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
@@ -3748,11 +3852,11 @@ bool QMetalTexture::create()
desc.textureType = isArray ? MTLTextureType1DArray : MTLTextureType1D;
} else if (isArray) {
#ifdef Q_OS_IOS
- if (samples > 1) {
- // would be available on iOS 14.0+ but cannot test for that with a 13 SDK
- qWarning("Multisample 2D texture array is not supported on iOS");
+ if (@available(iOS 14, *)) {
+ desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
+ } else {
+ desc.textureType = MTLTextureType2DArray;
}
- desc.textureType = MTLTextureType2DArray;
#else
desc.textureType = samples > 1 ? MTLTextureType2DMultisampleArray : MTLTextureType2DArray;
#endif
@@ -4161,8 +4265,9 @@ bool QMetalTextureRenderTarget::create()
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;
+ d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format());
+ d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture();
+ d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
if (d->colorAttCount == 0) {
d->pixelSize = depthTexD->pixelSize();
d->sampleCount = depthTexD->samples;
@@ -4172,16 +4277,24 @@ bool QMetalTextureRenderTarget::create()
d->fb.dsTex = depthRbD->d->tex;
d->fb.hasStencil = true;
d->fb.depthNeedsStore = false;
+ d->fb.preserveDs = false;
if (d->colorAttCount == 0) {
d->pixelSize = depthRbD->pixelSize();
d->sampleCount = depthRbD->samples;
}
}
+ if (m_desc.depthResolveTexture()) {
+ QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture());
+ d->fb.dsResolveTex = depthResolveTexD->d->tex;
+ }
d->dsAttCount = 1;
} else {
d->dsAttCount = 0;
}
+ if (d->colorAttCount > 0)
+ d->fb.preserveColor = m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+
QRhiRenderTargetAttachmentTracker::updateResIdList<QMetalTexture, QMetalRenderBuffer>(m_desc, &d->currentResIdList);
rhiD->registerResource(this, false);
@@ -4293,10 +4406,10 @@ void QMetalGraphicsPipeline::destroy()
d->tess.compTesc.destroy();
d->tess.vertTese.destroy();
- qDeleteAll(d->tess.deviceLocalWorkBuffers);
- d->tess.deviceLocalWorkBuffers.clear();
- qDeleteAll(d->tess.hostVisibleWorkBuffers);
- d->tess.hostVisibleWorkBuffers.clear();
+ qDeleteAll(d->extraBufMgr.deviceLocalWorkBuffers);
+ d->extraBufMgr.deviceLocalWorkBuffers.clear();
+ qDeleteAll(d->extraBufMgr.hostVisibleWorkBuffers);
+ d->extraBufMgr.hostVisibleWorkBuffers.clear();
delete d->bufferSizeBuffer;
d->bufferSizeBuffer = nullptr;
@@ -4368,6 +4481,22 @@ static inline MTLVertexFormat toMetalAttributeFormat(QRhiVertexInputAttribute::F
return MTLVertexFormatHalf2;
case QRhiVertexInputAttribute::Half:
return MTLVertexFormatHalf;
+ case QRhiVertexInputAttribute::UShort4:
+ return MTLVertexFormatUShort4;
+ case QRhiVertexInputAttribute::UShort3:
+ return MTLVertexFormatUShort3;
+ case QRhiVertexInputAttribute::UShort2:
+ return MTLVertexFormatUShort2;
+ case QRhiVertexInputAttribute::UShort:
+ return MTLVertexFormatUShort;
+ case QRhiVertexInputAttribute::SShort4:
+ return MTLVertexFormatShort4;
+ case QRhiVertexInputAttribute::SShort3:
+ return MTLVertexFormatShort3;
+ case QRhiVertexInputAttribute::SShort2:
+ return MTLVertexFormatShort2;
+ case QRhiVertexInputAttribute::SShort:
+ return MTLVertexFormatShort;
default:
Q_UNREACHABLE();
return MTLVertexFormatFloat4;
@@ -4523,6 +4652,24 @@ static inline MTLPrimitiveType toMetalPrimitiveType(QRhiGraphicsPipeline::Topolo
}
}
+static inline MTLPrimitiveTopologyClass toMetalPrimitiveTopologyClass(QRhiGraphicsPipeline::Topology t)
+{
+ switch (t) {
+ case QRhiGraphicsPipeline::Triangles:
+ case QRhiGraphicsPipeline::TriangleStrip:
+ case QRhiGraphicsPipeline::TriangleFan:
+ return MTLPrimitiveTopologyClassTriangle;
+ case QRhiGraphicsPipeline::Lines:
+ case QRhiGraphicsPipeline::LineStrip:
+ return MTLPrimitiveTopologyClassLine;
+ case QRhiGraphicsPipeline::Points:
+ return MTLPrimitiveTopologyClassPoint;
+ default:
+ Q_UNREACHABLE();
+ return MTLPrimitiveTopologyClassTriangle;
+ }
+}
+
static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c)
{
switch (c) {
@@ -4710,7 +4857,7 @@ void QMetalGraphicsPipeline::setupAttachmentsInMetalRenderPassDescriptor(void *m
}
QRHI_RES_RHI(QRhiMetal);
- rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
+ rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
}
void QMetalGraphicsPipeline::setupMetalDepthStencilDescriptor(void *metalDsDesc)
@@ -4763,6 +4910,7 @@ void QMetalGraphicsPipelineData::setupVertexInputDescriptor(MTLVertexDescriptor
desc.attributes[loc].bufferIndex = NSUInteger(firstVertexBinding + it->binding());
}
int bindingIndex = 0;
+ const NSUInteger viewCount = qMax<NSUInteger>(1, q->multiViewCount());
for (auto it = vertexInputLayout.cbeginBindings(), itEnd = vertexInputLayout.cendBindings();
it != itEnd; ++it, ++bindingIndex)
{
@@ -4771,6 +4919,8 @@ void QMetalGraphicsPipelineData::setupVertexInputDescriptor(MTLVertexDescriptor
it->classification() == QRhiVertexInputBinding::PerInstance
? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
desc.layouts[layoutIdx].stepRate = NSUInteger(it->instanceStepRate());
+ if (desc.layouts[layoutIdx].stepFunction == MTLVertexStepFunctionPerInstance)
+ desc.layouts[layoutIdx].stepRate *= viewCount;
desc.layouts[layoutIdx].stride = it->stride();
}
}
@@ -4819,33 +4969,10 @@ void QRhiMetalData::trySeedingRenderPipelineFromBinaryArchive(MTLRenderPipelineD
}
}
-static bool canAddToBinaryArchive(QRhiMetalData *d)
-{
- if (@available(macOS 11.0, iOS 14.0, *)) {
- if (!d->binArch)
- return false;
-
- // ### QTBUG-106703, QTBUG-108216, revisit after 13.0
- if (!d->binArchWasEmpty && d->q->osMajor >= 13) {
- static bool logPrinted = false;
- if (!logPrinted) {
- logPrinted = true;
- qCDebug(QRHI_LOG_INFO, "Skipping adding more pipelines to MTLBinaryArchive on this OS version (%d.%d) due to known issues.",
- d->q->osMajor, d->q->osMinor);
- }
- return false;
- }
-
- return true;
- } else {
- return false;
- }
-}
-
void QRhiMetalData::addRenderPipelineToBinaryArchive(MTLRenderPipelineDescriptor *rpDesc)
{
if (@available(macOS 11.0, iOS 14.0, *)) {
- if (canAddToBinaryArchive(this)) {
+ if (binArch) {
NSError *err = nil;
if (![binArch addRenderPipelineFunctionsWithDescriptor: rpDesc error: &err]) {
const QString msg = QString::fromNSString(err.localizedDescription);
@@ -4947,6 +5074,9 @@ bool QMetalGraphicsPipeline::createVertexFragmentPipeline()
QMetalRenderPassDescriptor *rpD = QRHI_RES(QMetalRenderPassDescriptor, m_renderPassDesc);
setupAttachmentsInMetalRenderPassDescriptor(rpDesc, rpD);
+ if (m_multiViewCount >= 2)
+ rpDesc.inputPrimitiveTopology = toMetalPrimitiveTopologyClass(m_topology);
+
rhiD->d->trySeedingRenderPipelineFromBinaryArchive(rpDesc);
if (rhiD->rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
@@ -5469,7 +5599,7 @@ id<MTLRenderPipelineState> QMetalGraphicsPipelineData::Tessellation::teseFragRen
return ps;
}
-QMetalBuffer *QMetalGraphicsPipelineData::Tessellation::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type)
+QMetalBuffer *QMetalGraphicsPipelineData::ExtraBufferManager::acquireWorkBuffer(QRhiMetal *rhiD, quint32 size, WorkBufType type)
{
QVector<QMetalBuffer *> *workBuffers = type == WorkBufType::DeviceLocal ? &deviceLocalWorkBuffers : &hostVisibleWorkBuffers;
@@ -5535,6 +5665,9 @@ bool QMetalGraphicsPipeline::createTessellationPipelines(const QShader &tessVert
return false;
}
+ if (m_multiViewCount >= 2)
+ qWarning("Multiview is not supported with tessellation");
+
// Now the vertex shader is a compute shader.
// It should have three dedicated *VertexAsComputeShader variants.
// What the requested variant was (Standard or Batchable) plays no role here.
@@ -5825,7 +5958,7 @@ void QRhiMetalData::trySeedingComputePipelineFromBinaryArchive(MTLComputePipelin
void QRhiMetalData::addComputePipelineToBinaryArchive(MTLComputePipelineDescriptor *cpDesc)
{
if (@available(macOS 11.0, iOS 14.0, *)) {
- if (canAddToBinaryArchive(this)) {
+ if (binArch) {
NSError *err = nil;
if (![binArch addComputePipelineFunctionsWithDescriptor: cpDesc error: &err]) {
const QString msg = QString::fromNSString(err.localizedDescription);
@@ -6042,6 +6175,7 @@ void QMetalSwapChain::destroy()
#endif
d->layer = nullptr;
+ m_proxyData = {};
[d->curDrawable release];
d->curDrawable = nil;
@@ -6105,9 +6239,17 @@ QSize QMetalSwapChain::surfacePixelSize()
bool QMetalSwapChain::isFormatSupported(Format f)
{
-#ifdef Q_OS_MACOS
- return f == SDR || f == HDRExtendedSrgbLinear;
-#endif
+ if (f == HDRExtendedSrgbLinear) {
+ if (@available(macOS 10.11, iOS 16.0, *))
+ return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
+ else
+ return false;
+ } else if (f == HDRExtendedDisplayP3Linear) {
+ if (@available(macOS 11.0, iOS 14.0, *))
+ return hdrInfo().limits.colorComponentValue.maxPotentialColorComponentValue > 1.0f;
+ else
+ return false;
+ }
return f == SDR;
}
@@ -6142,7 +6284,7 @@ void QMetalSwapChain::chooseFormats()
QRHI_RES_RHI(QRhiMetal);
samples = rhiD->effectiveSampleCount(m_sampleCount);
// pick a format that is allowed for CAMetalLayer.pixelFormat
- if (m_format == HDRExtendedSrgbLinear) {
+ if (m_format == HDRExtendedSrgbLinear || m_format == HDRExtendedDisplayP3Linear) {
d->colorFormat = MTLPixelFormatRGBA16Float;
d->rhiColorFormat = QRhiTexture::RGBA16F;
return;
@@ -6189,13 +6331,18 @@ bool QMetalSwapChain::createOrResize()
chooseFormats();
if (d->colorFormat != d->layer.pixelFormat)
d->layer.pixelFormat = d->colorFormat;
-#ifdef Q_OS_MACOS
- // Can't enable this on iOS until wantsExtendedDynamicRangeContent is available
+
if (m_format == HDRExtendedSrgbLinear) {
- d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
- d->layer.wantsExtendedDynamicRangeContent = YES;
+ if (@available(macOS 10.11, iOS 16.0, *)) {
+ d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearSRGB);
+ d->layer.wantsExtendedDynamicRangeContent = YES;
+ }
+ } else if (m_format == HDRExtendedDisplayP3Linear) {
+ if (@available(macOS 11.0, iOS 16.0, *)) {
+ d->layer.colorspace = CGColorSpaceCreateWithName(kCGColorSpaceExtendedLinearDisplayP3);
+ d->layer.wantsExtendedDynamicRangeContent = YES;
+ }
}
-#endif
if (m_flags.testFlag(UsedAsTransferSource))
d->layer.framebufferOnly = NO;
@@ -6328,22 +6475,28 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info;
info.limitsType = QRhiSwapChainHdrInfo::ColorComponentValue;
- if (m_format == SDR) {
- info.limits.colorComponentValue.maxColorComponentValue = 1;
- return info;
- }
+ info.limits.colorComponentValue.maxColorComponentValue = 1;
+ info.limits.colorComponentValue.maxPotentialColorComponentValue = 1;
+ info.luminanceBehavior = QRhiSwapChainHdrInfo::DisplayReferred; // 1.0 = SDR white
+ info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred
-#ifdef Q_OS_MACOS
- info.isHardCodedDefaults = false;
- // Must use m_window, not window, given this may be called before createOrResize().
- NSView *view = reinterpret_cast<NSView *>(m_window->winId());
- info.limits.colorComponentValue.maxColorComponentValue = view.window.screen.maximumExtendedDynamicRangeColorComponentValue;
-#else
- // ### Fixme: Maybe retrieve the brightness from the screen and if we're not at full brightness we might be able to do more.
- // For now, assume 2, in line with iPhone 12 specs that claim 625 nits max brightness and 1200 nits max HDR brightness.
- info.isHardCodedDefaults = true;
- info.limits.colorComponentValue.maxColorComponentValue = 2;
+ if (m_window) {
+ // Must use m_window, not window, given this may be called before createOrResize().
+#if defined(Q_OS_MACOS)
+ NSView *view = reinterpret_cast<NSView *>(m_window->winId());
+ NSScreen *screen = view.window.screen;
+ info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue;
+ info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
+#elif defined(Q_OS_IOS)
+ if (@available(iOS 16.0, *)) {
+ UIView *view = reinterpret_cast<UIView *>(m_window->winId());
+ UIScreen *screen = view.window.windowScene.screen;
+ info.limits.colorComponentValue.maxColorComponentValue = view.window.windowScene.screen.currentEDRHeadroom;
+ info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.potentialEDRHeadroom;
+ }
#endif
+ }
+
return info;
}
diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h
index 8fb2ce84b0..f539148b2c 100644
--- a/src/gui/rhi/qrhimetal_p.h
+++ b/src/gui/rhi/qrhimetal_p.h
@@ -454,7 +454,6 @@ public:
const QRhiCommandBuffer::DynamicOffset *dynamicOffsets,
bool offsetOnlyChange,
const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]);
- int effectiveSampleCount(int sampleCount) const;
struct TessDrawArgs {
QMetalCommandBuffer *cbD;
enum {
@@ -482,6 +481,7 @@ public:
};
};
void tessellatedDraw(const TessDrawArgs &args);
+ void adjustForMultiViewDraw(quint32 *instanceCount, QRhiCommandBuffer *cb);
QRhi::Flags rhiFlags;
bool importedDevice = false;
@@ -499,6 +499,7 @@ public:
QVector<int> supportedSampleCounts;
bool isAppleGPU = false;
int maxThreadGroupSize = 512;
+ bool multiView = false;
} caps;
QRhiMetalData *d = nullptr;
diff --git a/src/gui/rhi/qrhinull.cpp b/src/gui/rhi/qrhinull.cpp
index 95106bfeb8..566b922c1b 100644
--- a/src/gui/rhi/qrhinull.cpp
+++ b/src/gui/rhi/qrhinull.cpp
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Null backend specific initialization parameters.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A Null QRhi needs no special parameters for initialization.
@@ -35,7 +35,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Empty.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index a78b459dd6..3dd3c57bd4 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qrhivulkan_p.h"
-#include "qrhivulkanext_p.h"
#include <qpa/qplatformvulkaninstance.h>
#define VMA_IMPLEMENTATION
@@ -64,7 +63,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Vulkan specific initialization parameters.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to
@@ -196,7 +195,7 @@ QT_BEGIN_NAMESPACE
\note Ownership of the Vulkan objects is never transferred.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -258,7 +257,7 @@ QT_BEGIN_NAMESPACE
\l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
\l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -274,7 +273,7 @@ QT_BEGIN_NAMESPACE
\since 6.6
\brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor.
- \note This a RHI API with limited compatibility guarantees, see \l QRhi
+ \note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
*/
@@ -335,7 +334,9 @@ QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice()
{
return {
QByteArrayLiteral("VK_KHR_swapchain"),
- QByteArrayLiteral("VK_EXT_vertex_attribute_divisor")
+ QByteArrayLiteral("VK_EXT_vertex_attribute_divisor"),
+ QByteArrayLiteral("VK_KHR_create_renderpass2"),
+ QByteArrayLiteral("VK_KHR_depth_stencil_resolve")
};
}
@@ -429,6 +430,8 @@ bool QRhiVulkan::create(QRhi::Flags flags)
for (const char *ext : inst->extensions())
qCDebug(QRHI_LOG_INFO, " %s", ext);
}
+
+ caps = {};
caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils"));
QList<VkQueueFamilyProperties> queueFamilyProps;
@@ -530,7 +533,65 @@ bool QRhiVulkan::create(QRhi::Flags flags)
driverInfoStruct.vendorId = physDevProperties.vendorID;
driverInfoStruct.deviceType = toRhiDeviceType(physDevProperties.deviceType);
- f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures);
+ bool featuresQueried = false;
+#ifdef VK_VERSION_1_1
+ VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {};
+ physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+#endif
+
+ // Vulkan >=1.2 headers at build time, >=1.2 implementation at run time
+#ifdef VK_VERSION_1_2
+ if (!featuresQueried) {
+ // Vulkan11Features, Vulkan12Features, etc. are only in Vulkan 1.2 and newer.
+ if (caps.apiVersion >= QVersionNumber(1, 2)) {
+ physDevFeatures11IfApi12OrNewer = {};
+ physDevFeatures11IfApi12OrNewer.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
+ physDevFeatures12 = {};
+ physDevFeatures12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
+#ifdef VK_VERSION_1_3
+ physDevFeatures13 = {};
+ physDevFeatures13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+#endif
+ physDevFeaturesChainable.pNext = &physDevFeatures11IfApi12OrNewer;
+ physDevFeatures11IfApi12OrNewer.pNext = &physDevFeatures12;
+#ifdef VK_VERSION_1_3
+ if (caps.apiVersion >= QVersionNumber(1, 3))
+ physDevFeatures12.pNext = &physDevFeatures13;
+#endif
+ f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable);
+ memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures));
+ featuresQueried = true;
+ }
+ }
+#endif // VK_VERSION_1_2
+
+ // Vulkan >=1.1 headers at build time, 1.1 implementation at run time
+#ifdef VK_VERSION_1_1
+ if (!featuresQueried) {
+ // Vulkan versioning nightmares: if the runtime API version is 1.1,
+ // there is no Vulkan11Features (introduced in 1.2+, the headers might
+ // have the types and structs, but the Vulkan implementation version at
+ // run time is what matters). But there are individual feature structs.
+ // For multiview, it is important to get this right since at the time of
+ // writing Quest 3 Android is a Vulkan 1.1 implementation at run time on
+ // the headset.
+ if (caps.apiVersion == QVersionNumber(1, 1)) {
+ multiviewFeaturesIfApi11 = {};
+ multiviewFeaturesIfApi11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES;
+ physDevFeaturesChainable.pNext = &multiviewFeaturesIfApi11;
+ f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable);
+ memcpy(&physDevFeatures, &physDevFeaturesChainable.features, sizeof(VkPhysicalDeviceFeatures));
+ featuresQueried = true;
+ }
+ }
+#endif
+
+ if (!featuresQueried) {
+ // If the API version at run time is 1.0 (or we are building with
+ // ancient 1.0 headers), then do the Vulkan 1.0 query.
+ f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures);
+ featuresQueried = true;
+ }
// Choose queue and create device, unless the device was specified in importParams.
if (!importedDevice) {
@@ -602,13 +663,28 @@ bool QRhiVulkan::create(QRhi::Flags flags)
}
}
- caps.vertexAttribDivisor = false;
+#ifdef VK_EXT_vertex_attribute_divisor
if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
if (hasPhysDevProp2) {
requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
caps.vertexAttribDivisor = true;
}
}
+#endif
+
+#ifdef VK_KHR_create_renderpass2
+ if (devExts.contains(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
+ caps.renderPass2KHR = true;
+ }
+#endif
+
+#ifdef VK_KHR_depth_stencil_resolve
+ if (devExts.contains(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
+ caps.depthStencilResolveKHR = true;
+ }
+#endif
for (const QByteArray &ext : requestedDeviceExtensions) {
if (!ext.isEmpty() && !requestedDevExts.contains(ext)) {
@@ -661,42 +737,27 @@ bool QRhiVulkan::create(QRhi::Flags flags)
// tessellationShader, geometryShader
// textureCompressionETC2, textureCompressionASTC_LDR, textureCompressionBC
-#ifdef VK_VERSION_1_2 // Vulkan11Features is only in Vulkan 1.2
- VkPhysicalDeviceFeatures2 physDevFeatures2 = {};
- physDevFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-
- VkPhysicalDeviceVulkan11Features features11 = {};
- features11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
- VkPhysicalDeviceVulkan12Features features12 = {};
- features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
-#ifdef VK_VERSION_1_3
- VkPhysicalDeviceVulkan13Features features13 = {};
- features13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
+#ifdef VK_VERSION_1_1
+ physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE;
#endif
-
- if (caps.apiVersion >= QVersionNumber(1, 2)) {
- physDevFeatures2.pNext = &features11;
- features11.pNext = &features12;
#ifdef VK_VERSION_1_3
- if (caps.apiVersion >= QVersionNumber(1, 3))
- features12.pNext = &features13;
+ physDevFeatures13.robustImageAccess = VK_FALSE;
#endif
- f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeatures2);
- physDevFeatures2.features.robustBufferAccess = VK_FALSE;
-#ifdef VK_VERSION_1_3
- features13.robustImageAccess = VK_FALSE;
+#ifdef VK_VERSION_1_1
+ if (caps.apiVersion >= QVersionNumber(1, 1)) {
+ // For a >=1.2 implementation at run time, this will enable all
+ // (1.0-1.3) features reported as supported, except the ones we turn
+ // off explicitly above. For a 1.1 implementation at run time, this
+ // only enables the 1.0 and multiview features reported as
+ // supported. We will not be bothering with the Vulkan 1.1
+ // individual feature struct nonsense.
+ devInfo.pNext = &physDevFeaturesChainable;
+ } else
#endif
-
- devInfo.pNext = &physDevFeatures2;
- }
-#endif // VK_VERSION_1_2
-
- VkPhysicalDeviceFeatures features;
- if (!devInfo.pNext) {
- memcpy(&features, &physDevFeatures, sizeof(features));
- features.robustBufferAccess = VK_FALSE;
- devInfo.pEnabledFeatures = &features;
+ {
+ physDevFeatures.robustBufferAccess = VK_FALSE;
+ devInfo.pEnabledFeatures = &physDevFeatures;
}
VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev);
@@ -706,6 +767,13 @@ bool QRhiVulkan::create(QRhi::Flags flags)
}
} else {
qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
+
+ // Here we have no way to tell if the extensions got enabled or not.
+ // Pretend it's all there and supported. If getProcAddress fails, we'll
+ // handle that gracefully.
+ caps.vertexAttribDivisor = true;
+ caps.renderPass2KHR = true;
+ caps.depthStencilResolveKHR = true;
}
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
@@ -753,6 +821,28 @@ bool QRhiVulkan::create(QRhi::Flags flags)
caps.nonFillPolygonMode = physDevFeatures.fillModeNonSolid;
+#ifdef VK_VERSION_1_2
+ if (caps.apiVersion >= QVersionNumber(1, 2))
+ caps.multiView = physDevFeatures11IfApi12OrNewer.multiview;
+#endif
+
+#ifdef VK_VERSION_1_1
+ if (caps.apiVersion == QVersionNumber(1, 1))
+ caps.multiView = multiviewFeaturesIfApi11.multiview;
+#endif
+
+ // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we
+ // have to support the case of 1.1 + extensions, in particular for the Quest
+ // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on
+ // the KHR extension for now.
+#ifdef VK_KHR_create_renderpass2
+ if (caps.renderPass2KHR) {
+ vkCreateRenderPass2KHR = reinterpret_cast<PFN_vkCreateRenderPass2KHR>(f->vkGetDeviceProcAddr(dev, "vkCreateRenderPass2KHR"));
+ if (!vkCreateRenderPass2KHR) // handle it gracefully, the caps flag may be incorrect when using an imported VkDevice
+ caps.renderPass2KHR = false;
+ }
+#endif
+
if (!importedAllocator) {
VmaVulkanFunctions funcs = {};
funcs.vkGetInstanceProcAddr = wrap_vkGetInstanceProcAddr;
@@ -766,12 +856,9 @@ bool QRhiVulkan::create(QRhi::Flags flags)
allocatorInfo.device = dev;
allocatorInfo.pVulkanFunctions = &funcs;
allocatorInfo.instance = inst->vkInstance();
- const QVersionNumber apiVer = inst->apiVersion();
- if (!apiVer.isNull()) {
- allocatorInfo.vulkanApiVersion = VK_MAKE_VERSION(apiVer.majorVersion(),
- apiVer.minorVersion(),
- apiVer.microVersion());
- }
+ allocatorInfo.vulkanApiVersion = VK_MAKE_VERSION(caps.apiVersion.majorVersion(),
+ caps.apiVersion.minorVersion(),
+ caps.apiVersion.microVersion());
VmaAllocator vmaallocator;
VkResult err = vmaCreateAllocator(&allocatorInfo, &vmaallocator);
if (err != VK_SUCCESS) {
@@ -1280,6 +1367,8 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD
rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
rpD->hasDepthStencil = hasDepthStencil;
+ rpD->hasDepthStencilResolve = false;
+ rpD->multiViewCount = 0;
if (hasDepthStencil) {
// clear on load + no store + lazy alloc + transient image should play
@@ -1349,28 +1438,190 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD
return true;
}
+struct MultiViewRenderPassSetupHelper
+{
+ bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap)
+ {
+ if (multiViewCount < 2)
+ return true;
+ if (!multiViewCap) {
+ qWarning("Cannot create multiview render pass without support for the Vulkan 1.1 multiview feature");
+ return false;
+ }
+#ifdef VK_VERSION_1_1
+ uint32_t allViewsMask = 0;
+ for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i)
+ allViewsMask |= (1 << i);
+ multiViewMask = allViewsMask;
+ multiViewCorrelationMask = allViewsMask;
+ multiViewInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
+ multiViewInfo.subpassCount = 1;
+ multiViewInfo.pViewMasks = &multiViewMask;
+ multiViewInfo.correlationMaskCount = 1;
+ multiViewInfo.pCorrelationMasks = &multiViewCorrelationMask;
+ rpInfo->pNext = &multiViewInfo;
+#endif
+ return true;
+ }
+
+#ifdef VK_VERSION_1_1
+ VkRenderPassMultiviewCreateInfo multiViewInfo = {};
+ uint32_t multiViewMask = 0;
+ uint32_t multiViewCorrelationMask = 0;
+#endif
+};
+
+#ifdef VK_KHR_create_renderpass2
+// Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2,
+// adding depth-stencil resolve support. Assumes a single subpass and no subpass
+// dependencies.
+struct RenderPass2SetupHelper
+{
+ bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) {
+ *rpInfo2 = {};
+
+ viewMask = 0;
+ if (multiViewCount >= 2) {
+ for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i)
+ viewMask |= (1 << i);
+ }
+
+ attDescs2.resize(rpInfo->attachmentCount);
+ for (qsizetype i = 0; i < attDescs2.count(); ++i) {
+ VkAttachmentDescription2KHR &att2(attDescs2[i]);
+ const VkAttachmentDescription &att(rpInfo->pAttachments[i]);
+ att2 = {};
+ att2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
+ att2.flags = att.flags;
+ att2.format = att.format;
+ att2.samples = att.samples;
+ att2.loadOp = att.loadOp;
+ att2.storeOp = att.storeOp;
+ att2.stencilLoadOp = att.stencilLoadOp;
+ att2.stencilStoreOp = att.stencilStoreOp;
+ att2.initialLayout = att.initialLayout;
+ att2.finalLayout = att.finalLayout;
+ }
+
+ attRefs2.clear();
+ subpass2 = {};
+ subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
+ const VkSubpassDescription &subpassDesc(rpInfo->pSubpasses[0]);
+ subpass2.flags = subpassDesc.flags;
+ subpass2.pipelineBindPoint = subpassDesc.pipelineBindPoint;
+ if (multiViewCount >= 2)
+ subpass2.viewMask = viewMask;
+
+ // color attachment refs
+ qsizetype startIndex = attRefs2.count();
+ for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(subpassDesc.pColorAttachments[j]);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ }
+ subpass2.colorAttachmentCount = subpassDesc.colorAttachmentCount;
+ subpass2.pColorAttachments = attRefs2.constData() + startIndex;
+
+ // color resolve refs
+ if (subpassDesc.pResolveAttachments) {
+ startIndex = attRefs2.count();
+ for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(subpassDesc.pResolveAttachments[j]);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ }
+ subpass2.pResolveAttachments = attRefs2.constData() + startIndex;
+ }
+
+ // depth-stencil ref
+ if (subpassDesc.pDepthStencilAttachment) {
+ startIndex = attRefs2.count();
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(*subpassDesc.pDepthStencilAttachment);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ subpass2.pDepthStencilAttachment = attRefs2.constData() + startIndex;
+ }
+
+ // depth-stencil resolve ref
+#ifdef VK_KHR_depth_stencil_resolve
+ dsResolveDesc = {};
+ if (rpD->hasDepthStencilResolve) {
+ startIndex = attRefs2.count();
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = rpD->dsResolveRef.attachment;
+ attref2.layout = rpD->dsResolveRef.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ dsResolveDesc.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR;
+ dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+ dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+ dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex;
+ subpass2.pNext = &dsResolveDesc;
+ }
+#endif
+
+ rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
+ rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs
+ rpInfo2->flags = rpInfo->flags;
+ rpInfo2->attachmentCount = rpInfo->attachmentCount;
+ rpInfo2->pAttachments = attDescs2.constData();
+ rpInfo2->subpassCount = 1;
+ rpInfo2->pSubpasses = &subpass2;
+ if (multiViewCount >= 2) {
+ rpInfo2->correlatedViewMaskCount = 1;
+ rpInfo2->pCorrelatedViewMasks = &viewMask;
+ }
+ return true;
+ }
+
+ QVarLengthArray<VkAttachmentDescription2KHR, 8> attDescs2;
+ QVarLengthArray<VkAttachmentReference2KHR, 8> attRefs2;
+ VkSubpassDescription2KHR subpass2;
+#ifdef VK_KHR_depth_stencil_resolve
+ VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc;
+#endif
+ uint32_t viewMask;
+};
+#endif // VK_KHR_create_renderpass2
+
bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
- const QRhiColorAttachment *firstColorAttachment,
- const QRhiColorAttachment *lastColorAttachment,
+ const QRhiColorAttachment *colorAttachmentsBegin,
+ const QRhiColorAttachment *colorAttachmentsEnd,
bool preserveColor,
bool preserveDs,
+ bool storeDs,
QRhiRenderBuffer *depthStencilBuffer,
- QRhiTexture *depthTexture)
+ QRhiTexture *depthTexture,
+ QRhiTexture *depthResolveTexture)
{
- // attachment list layout is color (0-8), ds (0-1), resolve (0-8)
+ // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1)
- for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
+ int multiViewCount = 0;
+ for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
Q_ASSERT(texD || rbD);
- const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat;
+ const VkFormat vkformat = texD ? texD->viewFormat : rbD->vkformat;
const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples;
VkAttachmentDescription attDesc = {};
attDesc.format = vkformat;
attDesc.samples = samples;
attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR;
- attDesc.storeOp = it->resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.storeOp = (it->resolveTexture() && !preserveColor) ? 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
@@ -1380,16 +1631,27 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
const VkAttachmentReference ref = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
rpD->colorRefs.append(ref);
+
+ if (it->multiViewCount() >= 2) {
+ if (multiViewCount > 0 && multiViewCount != it->multiViewCount())
+ qWarning("Inconsistent multiViewCount in color attachment set");
+ else
+ multiViewCount = it->multiViewCount();
+ } else if (multiViewCount > 0) {
+ qWarning("Mixing non-multiview color attachments within a multiview render pass");
+ }
}
+ Q_ASSERT(multiViewCount == 0 || multiViewCount >= 2);
+ rpD->multiViewCount = uint32_t(multiViewCount);
rpD->hasDepthStencil = depthStencilBuffer || depthTexture;
if (rpD->hasDepthStencil) {
- const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat
+ const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->viewFormat
: 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;
+ const VkAttachmentStoreOp storeOp = storeDs ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
VkAttachmentDescription attDesc = {};
attDesc.format = dsFormat;
attDesc.samples = samples;
@@ -1397,13 +1659,17 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
attDesc.storeOp = storeOp;
attDesc.stencilLoadOp = loadOp;
attDesc.stencilStoreOp = storeOp;
- attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.initialLayout = preserveDs ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
rpD->attDescs.append(attDesc);
+ if (depthTexture && depthTexture->arraySize() >= 2 && colorAttachmentsBegin == colorAttachmentsEnd) {
+ multiViewCount = depthTexture->arraySize();
+ rpD->multiViewCount = multiViewCount;
+ }
}
rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
- for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
+ for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
if (it->resolveTexture()) {
QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture());
const VkFormat dstFormat = rtexD->vkformat;
@@ -1422,7 +1688,7 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
}
VkAttachmentDescription attDesc = {};
- attDesc.format = dstFormat;
+ attDesc.format = rtexD->viewFormat;
attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
@@ -1441,6 +1707,31 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
}
Q_ASSERT(rpD->colorRefs.size() == rpD->resolveRefs.size());
+ rpD->hasDepthStencilResolve = rpD->hasDepthStencil && depthResolveTexture;
+ if (rpD->hasDepthStencilResolve) {
+ QVkTexture *rtexD = QRHI_RES(QVkTexture, depthResolveTexture);
+ if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
+ qWarning("Resolving into a multisample depth texture is not supported");
+
+ QVkTexture *texD = QRHI_RES(QVkTexture, depthResolveTexture);
+ if (texD->vkformat != rtexD->vkformat) {
+ qWarning("Multisample resolve between different depth-stencil formats (%d and %d) is not supported.",
+ int(texD->vkformat), int(rtexD->vkformat));
+ }
+
+ VkAttachmentDescription attDesc = {};
+ attDesc.format = rtexD->viewFormat;
+ 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 = attDesc.loadOp;
+ attDesc.stencilStoreOp = attDesc.storeOp;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ rpD->attDescs.append(attDesc);
+ }
+ rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
// rpD->subpassDeps stays empty: 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 and activateTextureRenderTarget() to generate
@@ -1450,10 +1741,35 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
VkSubpassDescription subpassDesc;
fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD);
- VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
- if (err != VK_SUCCESS) {
- qWarning("Failed to create renderpass: %d", err);
+ MultiViewRenderPassSetupHelper multiViewHelper;
+ if (!multiViewHelper.prepare(&rpInfo, multiViewCount, caps.multiView))
return false;
+
+#ifdef VK_KHR_create_renderpass2
+ if (rpD->hasDepthStencilResolve && caps.renderPass2KHR) {
+ // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1.
+ VkRenderPassCreateInfo2KHR rpInfo2;
+ RenderPass2SetupHelper rp2Helper;
+ if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount))
+ return false;
+
+ VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err);
+ return false;
+ }
+ } else
+#endif
+ {
+ if (rpD->hasDepthStencilResolve) {
+ qWarning("Resolving multisample depth-stencil buffers is not supported without "
+ "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2");
+ }
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
}
return true;
@@ -1540,9 +1856,16 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(QRhiSwapChain::UsedAsTransferSource))
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+ const bool stereo = bool(swapChainD->m_window) && (swapChainD->m_window->format().stereo())
+ && surfaceCaps.maxImageArrayLayers > 1;
+ swapChainD->stereo = stereo;
+
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
if (swapChainD->m_flags.testFlag(QRhiSwapChain::NoVSync)) {
- if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR))
+ // Stereo has a weird bug, when using VK_PRESENT_MODE_MAILBOX_KHR,
+ // black screen is shown, but there is no validation error.
+ // Detected on Windows, with NVidia RTX A series (at least 4000 and 6000) driver 535.98
+ if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_MAILBOX_KHR) && !stereo)
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
else if (swapChainD->supportedPresentationModes.contains(VK_PRESENT_MODE_IMMEDIATE_KHR))
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
@@ -1566,7 +1889,7 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
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.imageArrayLayers = stereo ? 2u : 1u;
swapChainInfo.imageUsage = usage;
swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapChainInfo.preTransform = preTransform;
@@ -1628,7 +1951,9 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
- swapChainD->imageRes.resize(swapChainD->bufferCount);
+ // Double up for stereo
+ swapChainD->imageRes.resize(swapChainD->bufferCount * (stereo ? 2u : 1u));
+
for (int i = 0; i < swapChainD->bufferCount; ++i) {
QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
image.image = swapChainImages[i];
@@ -1656,6 +1981,36 @@ bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain)
image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone;
}
+ if (stereo) {
+ for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(swapChainD->imageRes[i + swapChainD->bufferCount]);
+ image.image = swapChainImages[i];
+ if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) {
+ image.msaaImage = msaaImages[i];
+ image.msaaImageView = msaaViews[i];
+ }
+
+ VkImageViewCreateInfo 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.baseArrayLayer = 1;
+ 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;
@@ -1723,7 +2078,7 @@ void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain)
}
}
- for (int i = 0; i < swapChainD->bufferCount; ++i) {
+ for (int i = 0; i < swapChainD->bufferCount * (swapChainD->stereo ? 2 : 1); ++i) {
QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]);
if (image.fb) {
df->vkDestroyFramebuffer(dev, image.fb, nullptr);
@@ -1855,6 +2210,12 @@ QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::Begin
QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]);
swapChainD->rtWrapper.d.fb = image.fb;
+ if (swapChainD->stereo) {
+ QVkSwapChain::ImageResources &image(
+ swapChainD->imageRes[swapChainD->currentImageIndex + swapChainD->bufferCount]);
+ swapChainD->rtWrapperRight.d.fb = image.fb;
+ }
+
prepareNewFrame(&swapChainD->cbWrapper);
// Read the timestamps for the previous frame for this slot.
@@ -2340,6 +2701,13 @@ void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRe
QRhiPassResourceTracker::TexDepthOutputStage);
depthTexD->lastActiveFrameSlot = currentFrameSlot;
}
+ if (rtD->m_desc.depthResolveTexture()) {
+ QVkTexture *depthResolveTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthResolveTexture());
+ trackedRegisterTexture(&passResTracker, depthResolveTexD,
+ QRhiPassResourceTracker::TexDepthOutput,
+ QRhiPassResourceTracker::TexDepthOutputStage);
+ depthResolveTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
}
void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
@@ -2481,6 +2849,11 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
float(colorClearValue.alphaF()) } };
cvs.append(cv);
}
+ for (int i = 0; i < rtD->dsResolveAttCount; ++i) {
+ VkClearValue cv;
+ cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
+ cvs.append(cv);
+ }
rpBeginInfo.clearValueCount = uint32_t(cvs.size());
QVkCommandBuffer::Command &cmd(cbD->commands.get());
@@ -2874,7 +3247,7 @@ void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, i
case QRhiShaderResourceBinding::ImageLoadStore:
{
QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex);
- VkImageView view = texD->imageViewForLevel(b->u.simage.level);
+ VkImageView view = texD->perLevelImageViewForLoadStore(b->u.simage.level);
if (view) {
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
bd.simage.id = texD->m_id;
@@ -3128,12 +3501,12 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
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 += VkDeviceSize(sy * image.bytesPerLine() + sx * 4);
- // bufferRowLength remains set to the original image's width
+
+ if (size.width() == image.width()) {
+ // No need to make a QImage copy here, can copy from the source
+ // QImage into staging directly.
+ src = image.constBits() + sy * image.bytesPerLine() + sx * bpc;
+ copySizeBytes = size.height() * image.bytesPerLine();
} else {
image = image.copy(sx, sy, size.width(), size.height());
src = image.constBits();
@@ -3196,6 +3569,12 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
}
}
+void QRhiVulkan::printExtraErrorInfo(VkResult err)
+{
+ if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY)
+ qWarning() << "Out of device memory, current allocator statistics are" << statistics();
+}
+
void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
{
QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
@@ -3233,6 +3612,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
bufD->stagingAllocations[currentFrameSlot] = allocation;
} else {
qWarning("Failed to create staging buffer of size %u: %d", bufD->m_size, err);
+ printExtraErrorInfo(err);
continue;
}
}
@@ -3321,6 +3701,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
readback.stagingAlloc = allocation;
} else {
qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
+ printExtraErrorInfo(err);
continue;
}
@@ -3370,6 +3751,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
&utexD->stagingBuffers[currentFrameSlot], &allocation, nullptr);
if (err != VK_SUCCESS) {
qWarning("Failed to create image staging buffer of size %d: %d", int(stagingSize), err);
+ printExtraErrorInfo(err);
continue;
}
utexD->stagingAllocations[currentFrameSlot] = allocation;
@@ -3526,6 +3908,7 @@ void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdat
readback.stagingAlloc = allocation;
} else {
qWarning("Failed to create readback buffer of size %u: %d", readback.byteSize, err);
+ printExtraErrorInfo(err);
continue;
}
@@ -3773,6 +4156,8 @@ void QRhiVulkan::executeDeferredReleases(bool forced)
df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr);
df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
}
+ df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr);
+ df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr);
break;
case QRhiVulkan::DeferredReleaseEntry::RenderPass:
df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
@@ -3882,18 +4267,12 @@ QList<int> QRhiVulkan::supportedSampleCounts() const
return result;
}
-VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount)
+VkSampleCountFlagBits QRhiVulkan::effectiveSampleCountBits(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;
- }
+ const int s = effectiveSampleCount(sampleCount);
for (const auto &qvk_sampleCount : qvk_sampleCounts) {
- if (qvk_sampleCount.count == sampleCount)
+ if (qvk_sampleCount.count == s)
return qvk_sampleCount.mask;
}
@@ -4436,6 +4815,12 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return true;
case QRhi::ThreeDimensionalTextureMipmaps:
return true;
+ case QRhi::MultiView:
+ return caps.multiView;
+ case QRhi::TextureViewFormat:
+ return true;
+ case QRhi::ResolveDepthStencil:
+ return caps.renderPass2KHR && caps.depthStencilResolveKHR;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -4568,6 +4953,7 @@ QByteArray QRhiVulkan::pipelineCacheData()
header.deviceId = physDevProperties.deviceID;
header.dataSize = quint32(dataSize);
header.uuidSize = VK_UUID_SIZE;
+ header.reserved = 0;
memcpy(data.data(), &header, headerSize);
memcpy(data.data() + headerSize, physDevProperties.pipelineCacheUUID, VK_UUID_SIZE);
@@ -5464,6 +5850,22 @@ static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format form
return VK_FORMAT_R16G16_SFLOAT;
case QRhiVertexInputAttribute::Half:
return VK_FORMAT_R16_SFLOAT;
+ case QRhiVertexInputAttribute::UShort4:
+ return VK_FORMAT_R16G16B16A16_UINT;
+ case QRhiVertexInputAttribute::UShort3:
+ return VK_FORMAT_R16G16B16_UINT;
+ case QRhiVertexInputAttribute::UShort2:
+ return VK_FORMAT_R16G16_UINT;
+ case QRhiVertexInputAttribute::UShort:
+ return VK_FORMAT_R16_UINT;
+ case QRhiVertexInputAttribute::SShort4:
+ return VK_FORMAT_R16G16B16A16_SINT;
+ case QRhiVertexInputAttribute::SShort3:
+ return VK_FORMAT_R16G16B16_SINT;
+ case QRhiVertexInputAttribute::SShort2:
+ return VK_FORMAT_R16G16_SINT;
+ case QRhiVertexInputAttribute::SShort:
+ return VK_FORMAT_R16_SINT;
default:
Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT);
}
@@ -5833,7 +6235,8 @@ bool QVkBuffer::create()
}
if (err != VK_SUCCESS) {
- qWarning("Failed to create buffer: %d", err);
+ qWarning("Failed to create buffer of size %u: %d", nonZeroSize, err);
+ rhiD->printExtraErrorInfo(err);
return false;
}
@@ -5941,7 +6344,7 @@ bool QVkRenderBuffer::create()
return false;
QRHI_RES_RHI(QRhiVulkan);
- samples = rhiD->effectiveSampleCount(m_sampleCount);
+ samples = rhiD->effectiveSampleCountBits(m_sampleCount);
switch (m_type) {
case QRhiRenderBuffer::Color:
@@ -6059,6 +6462,15 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize)
QRHI_RES_RHI(QRhiVulkan);
vkformat = toVkTextureFormat(m_format, m_flags);
+ if (m_writeViewFormat.format != UnknownFormat)
+ viewFormat = toVkTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags());
+ else
+ viewFormat = vkformat;
+ if (m_readViewFormat.format != UnknownFormat)
+ viewFormatForSampling = toVkTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags());
+ else
+ viewFormatForSampling = vkformat;
+
VkFormatProperties props;
rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props);
const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
@@ -6082,7 +6494,7 @@ bool QVkTexture::prepareCreate(QSize *adjustedSize)
qWarning("Too many mip levels (%d, max is %d), truncating mip chain", mipLevelCount, maxLevels);
mipLevelCount = maxLevels;
}
- samples = rhiD->effectiveSampleCount(m_sampleCount);
+ samples = rhiD->effectiveSampleCountBits(m_sampleCount);
if (samples > VK_SAMPLE_COUNT_1_BIT) {
if (isCube) {
qWarning("Cubemap texture cannot be multisample");
@@ -6154,7 +6566,7 @@ bool QVkTexture::finishCreate()
: (is3D ? VK_IMAGE_VIEW_TYPE_3D
: (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D)
: (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)));
- viewInfo.format = vkformat;
+ viewInfo.format = viewFormatForSampling;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
@@ -6246,7 +6658,14 @@ bool QVkTexture::create()
VmaAllocation allocation;
VkResult err = vmaCreateImage(toVmaAllocator(rhiD->allocator), &imageInfo, &allocInfo, &image, &allocation, nullptr);
if (err != VK_SUCCESS) {
- qWarning("Failed to create image: %d", err);
+ qWarning("Failed to create image (with VkImageCreateInfo %ux%u depth %u vkformat 0x%X mips %u layers %u vksamples 0x%X): %d",
+ imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth,
+ int(imageInfo.format),
+ imageInfo.mipLevels,
+ imageInfo.arrayLayers,
+ int(imageInfo.samples),
+ err);
+ rhiD->printExtraErrorInfo(err);
return false;
}
imageAlloc = allocation;
@@ -6293,7 +6712,7 @@ void QVkTexture::setNativeLayout(int layout)
usageState.layout = VkImageLayout(layout);
}
-VkImageView QVkTexture::imageViewForLevel(int level)
+VkImageView QVkTexture::perLevelImageViewForLoadStore(int level)
{
Q_ASSERT(level >= 0 && level < int(mipLevelCount));
if (perLevelImageViews[level] != VK_NULL_HANDLE)
@@ -6313,7 +6732,7 @@ VkImageView QVkTexture::imageViewForLevel(int level)
: (is3D ? VK_IMAGE_VIEW_TYPE_3D
: (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D)
: (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D)));
- viewInfo.format = vkformat;
+ viewInfo.format = viewFormat; // this is writeViewFormat, regardless of Load, Store, or LoadStore; intentional
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
@@ -6400,7 +6819,7 @@ bool QVkSampler::create()
QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi)
: QRhiRenderPassDescriptor(rhi)
{
- serializedFormatData.reserve(32);
+ serializedFormatData.reserve(64);
}
QVkRenderPassDescriptor::~QVkRenderPassDescriptor()
@@ -6463,6 +6882,10 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other
return false;
if (hasDepthStencil != o->hasDepthStencil)
return false;
+ if (hasDepthStencilResolve != o->hasDepthStencilResolve)
+ return false;
+ if (multiViewCount != o->multiViewCount)
+ return false;
for (int i = 0, ie = colorRefs.size(); i != ie; ++i) {
const uint32_t attIdx = colorRefs[i].attachment;
@@ -6488,6 +6911,14 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other
return false;
}
+ if (hasDepthStencilResolve) {
+ const uint32_t attIdx = dsResolveRef.attachment;
+ if (attIdx != o->dsResolveRef.attachment)
+ return false;
+ if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
+ return false;
+ }
+
// subpassDeps is not included
return true;
@@ -6502,6 +6933,8 @@ void QVkRenderPassDescriptor::updateSerializedFormat()
*p++ = colorRefs.size();
*p++ = resolveRefs.size();
*p++ = hasDepthStencil;
+ *p++ = hasDepthStencilResolve;
+ *p++ = multiViewCount;
auto serializeAttachmentData = [this, &p](uint32_t attIdx) {
const bool used = attIdx != VK_ATTACHMENT_UNUSED;
@@ -6533,6 +6966,12 @@ void QVkRenderPassDescriptor::updateSerializedFormat()
*p++ = attIdx;
serializeAttachmentData(attIdx);
}
+
+ if (hasDepthStencilResolve) {
+ const uint32_t attIdx = dsResolveRef.attachment;
+ *p++ = attIdx;
+ serializeAttachmentData(attIdx);
+ }
}
QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const
@@ -6545,13 +6984,22 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri
rpD->resolveRefs = resolveRefs;
rpD->subpassDeps = subpassDeps;
rpD->hasDepthStencil = hasDepthStencil;
+ rpD->hasDepthStencilResolve = hasDepthStencilResolve;
+ rpD->multiViewCount = multiViewCount;
rpD->dsRef = dsRef;
+ rpD->dsResolveRef = dsResolveRef;
VkRenderPassCreateInfo rpInfo;
VkSubpassDescription subpassDesc;
fillRenderPassCreateInfo(&rpInfo, &subpassDesc, rpD);
QRHI_RES_RHI(QRhiVulkan);
+ MultiViewRenderPassSetupHelper multiViewHelper;
+ if (!multiViewHelper.prepare(&rpInfo, multiViewCount, rhiD->caps.multiView)) {
+ delete rpD;
+ return nullptr;
+ }
+
VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp);
if (err != VK_SUCCESS) {
qWarning("Failed to create renderpass: %d", err);
@@ -6640,6 +7088,11 @@ void QVkTextureRenderTarget::destroy()
resrtv[att] = VK_NULL_HANDLE;
}
+ e.textureRenderTarget.dsv = dsv;
+ dsv = VK_NULL_HANDLE;
+ e.textureRenderTarget.resdsv = resdsv;
+ resdsv = VK_NULL_HANDLE;
+
QRHI_RES_RHI(QRhiVulkan);
if (rhiD) {
rhiD->releaseQueue.append(e);
@@ -6658,8 +7111,10 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip
m_desc.cendColorAttachments(),
m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents),
+ m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(),
m_desc.depthStencilBuffer(),
- m_desc.depthTexture()))
+ m_desc.depthTexture(),
+ m_desc.depthResolveTexture()))
{
delete rp;
return nullptr;
@@ -6682,6 +7137,7 @@ bool QVkTextureRenderTarget::create()
QRHI_RES_RHI(QRhiVulkan);
QVarLengthArray<VkImageView, 8> views;
+ d.multiViewCount = 0;
d.colorAttCount = 0;
int attIndex = 0;
@@ -6692,13 +7148,17 @@ bool QVkTextureRenderTarget::create()
Q_ASSERT(texD || rbD);
if (texD) {
Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget));
+ const bool is1D = texD->flags().testFlag(QRhiTexture::OneDimensional);
+ const bool isMultiView = it->multiViewCount() >= 2;
+ if (isMultiView && d.multiViewCount == 0)
+ d.multiViewCount = it->multiViewCount();
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = texD->image;
- viewInfo.viewType = texD->flags().testFlag(QRhiTexture::OneDimensional)
- ? VK_IMAGE_VIEW_TYPE_1D
- : VK_IMAGE_VIEW_TYPE_2D;
- viewInfo.format = texD->vkformat;
+ viewInfo.viewType = is1D ? VK_IMAGE_VIEW_TYPE_1D
+ : (isMultiView ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
+ : VK_IMAGE_VIEW_TYPE_2D);
+ viewInfo.format = texD->viewFormat;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
@@ -6707,7 +7167,7 @@ bool QVkTextureRenderTarget::create()
viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level());
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer());
- viewInfo.subresourceRange.layerCount = 1;
+ viewInfo.subresourceRange.layerCount = uint32_t(isMultiView ? it->multiViewCount() : 1);
VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]);
if (err != VK_SUCCESS) {
qWarning("Failed to create render target image view: %d", err);
@@ -6732,7 +7192,25 @@ bool QVkTextureRenderTarget::create()
if (hasDepthStencil) {
if (m_desc.depthTexture()) {
QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture());
- views.append(depthTexD->imageView);
+ // need a dedicated view just because viewFormat may differ from vkformat
+ VkImageViewCreateInfo viewInfo = {};
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = depthTexD->image;
+ viewInfo.viewType = d.multiViewCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = depthTexD->viewFormat;
+ 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_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &dsv);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create depth-stencil image view for rt: %d", err);
+ return false;
+ }
+ views.append(dsv);
if (d.colorAttCount == 0) {
d.pixelSize = depthTexD->pixelSize();
d.sampleCount = depthTexD->samples;
@@ -6752,6 +7230,7 @@ bool QVkTextureRenderTarget::create()
d.resolveAttCount = 0;
attIndex = 0;
+ Q_ASSERT(d.multiViewCount == 0 || d.multiViewCount >= 2);
for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
if (it->resolveTexture()) {
QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture());
@@ -6761,8 +7240,9 @@ bool QVkTextureRenderTarget::create()
VkImageViewCreateInfo 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.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
+ : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = resTexD->viewFormat;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
@@ -6771,7 +7251,7 @@ bool QVkTextureRenderTarget::create()
viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel());
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer());
- viewInfo.subresourceRange.layerCount = 1;
+ viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]);
if (err != VK_SUCCESS) {
qWarning("Failed to create render target resolve image view: %d", err);
@@ -6781,6 +7261,36 @@ bool QVkTextureRenderTarget::create()
}
}
+ if (m_desc.depthResolveTexture()) {
+ QVkTexture *resTexD = QRHI_RES(QVkTexture, m_desc.depthResolveTexture());
+ Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
+
+ VkImageViewCreateInfo viewInfo = {};
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = resTexD->image;
+ viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
+ : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = resTexD->viewFormat;
+ 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_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ viewInfo.subresourceRange.baseMipLevel = 0;
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = 0;
+ viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resdsv);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target depth resolve image view: %d", err);
+ return false;
+ }
+ views.append(resdsv);
+ d.dsResolveAttCount = 1;
+ } else {
+ d.dsResolveAttCount = 0;
+ }
+
if (!m_renderPassDesc)
qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
@@ -6790,7 +7300,7 @@ bool QVkTextureRenderTarget::create()
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = d.rp->rp;
- fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount);
+ fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount + d.dsResolveAttCount);
fbInfo.pAttachments = views.constData();
fbInfo.width = uint32_t(d.pixelSize.width());
fbInfo.height = uint32_t(d.pixelSize.height());
@@ -7049,7 +7559,9 @@ bool QVkGraphicsPipeline::create()
pipelineInfo.pStages = shaderStageCreateInfos.constData();
QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings;
+#ifdef VK_EXT_vertex_attribute_divisor
QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates;
+#endif
int bindingIndex = 0;
for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings();
it != itEnd; ++it, ++bindingIndex)
@@ -7061,9 +7573,12 @@ bool QVkGraphicsPipeline::create()
? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE
};
if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) {
+#ifdef VK_EXT_vertex_attribute_divisor
if (rhiD->caps.vertexAttribDivisor) {
nonOneStepRates.append({ uint32_t(bindingIndex), it->instanceStepRate() });
- } else {
+ } else
+#endif
+ {
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");
@@ -7089,13 +7604,15 @@ bool QVkGraphicsPipeline::create()
vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData();
vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.size());
vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData();
+#ifdef VK_EXT_vertex_attribute_divisor
VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo = {};
if (!nonOneStepRates.isEmpty()) {
- divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
+ divisorInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT;
divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.size());
divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData();
vertexInputInfo.pNext = &divisorInfo;
}
+#endif
pipelineInfo.pVertexInputState = &vertexInputInfo;
QVarLengthArray<VkDynamicState, 8> dynEnable;
@@ -7166,7 +7683,7 @@ bool QVkGraphicsPipeline::create()
VkPipelineMultisampleStateCreateInfo msInfo = {};
msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
- msInfo.rasterizationSamples = rhiD->effectiveSampleCount(m_sampleCount);
+ msInfo.rasterizationSamples = rhiD->effectiveSampleCountBits(m_sampleCount);
pipelineInfo.pMultisampleState = &msInfo;
VkPipelineDepthStencilStateCreateInfo dsInfo = {};
@@ -7365,6 +7882,7 @@ const QRhiNativeHandles *QVkCommandBuffer::nativeHandles()
QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi)
: QRhiSwapChain(rhi),
rtWrapper(rhi, this),
+ rtWrapperRight(rhi, this),
cbWrapper(rhi)
{
}
@@ -7407,6 +7925,11 @@ QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget()
return &rtWrapper;
}
+QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
+{
+ return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
+}
+
QSize QVkSwapChain::surfacePixelSize()
{
if (!ensureSurface())
@@ -7434,6 +7957,9 @@ static inline bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, cons
case QRhiSwapChain::HDR10:
return (s.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || s.format == VK_FORMAT_A2R10G10B10_UNORM_PACK32)
&& s.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT;
+ case QRhiSwapChain::HDRExtendedDisplayP3Linear:
+ return s.format == VK_FORMAT_R16G16B16A16_SFLOAT
+ && s.colorSpace == VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT;
default:
break;
}
@@ -7554,7 +8080,7 @@ bool QVkSwapChain::ensureSurface()
}
}
- samples = rhiD->effectiveSampleCount(m_sampleCount);
+ samples = rhiD->effectiveSampleCountBits(m_sampleCount);
quint32 presModeCount = 0;
rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr);
@@ -7625,6 +8151,7 @@ bool QVkSwapChain::createOrResize()
rtWrapper.d.dsAttCount = 0;
ds = nullptr;
}
+ rtWrapper.d.dsResolveAttCount = 0;
if (samples > VK_SAMPLE_COUNT_1_BIT)
rtWrapper.d.resolveAttCount = 1;
else
@@ -7641,7 +8168,7 @@ bool QVkSwapChain::createOrResize()
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = rtWrapper.d.rp->rp;
- fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount);
+ fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount + rtWrapper.d.dsResolveAttCount);
fbInfo.pAttachments = views;
fbInfo.width = uint32_t(pixelSize.width());
fbInfo.height = uint32_t(pixelSize.height());
@@ -7654,6 +8181,56 @@ bool QVkSwapChain::createOrResize()
}
}
+ if (stereo) {
+ rtWrapperRight.setRenderPassDescriptor(
+ m_renderPassDesc); // for the public getter in QRhiRenderTarget
+ rtWrapperRight.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc);
+ Q_ASSERT(rtWrapperRight.d.rp && rtWrapperRight.d.rp->rp);
+
+ rtWrapperRight.d.pixelSize = pixelSize;
+ rtWrapperRight.d.dpr = float(window->devicePixelRatio());
+ rtWrapperRight.d.sampleCount = samples;
+ rtWrapperRight.d.colorAttCount = 1;
+ if (m_depthStencil) {
+ rtWrapperRight.d.dsAttCount = 1;
+ ds = QRHI_RES(QVkRenderBuffer, m_depthStencil);
+ } else {
+ rtWrapperRight.d.dsAttCount = 0;
+ ds = nullptr;
+ }
+ rtWrapperRight.d.dsResolveAttCount = 0;
+ if (samples > VK_SAMPLE_COUNT_1_BIT)
+ rtWrapperRight.d.resolveAttCount = 1;
+ else
+ rtWrapperRight.d.resolveAttCount = 0;
+
+ for (int i = 0; i < bufferCount; ++i) {
+ QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]);
+ 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 = {};
+ fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbInfo.renderPass = rtWrapperRight.d.rp->rp;
+ fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount
+ + rtWrapperRight.d.resolveAttCount + rtWrapperRight.d.dsResolveAttCount);
+ fbInfo.pAttachments = views;
+ fbInfo.width = uint32_t(pixelSize.width());
+ fbInfo.height = uint32_t(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;
if (needsRegistration)
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index 7ba1b8c89b..f23d8550f0 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -103,7 +103,7 @@ struct QVkTexture : public QRhiTexture
bool prepareCreate(QSize *adjustedSize = nullptr);
bool finishCreate();
- VkImageView imageViewForLevel(int level);
+ VkImageView perLevelImageViewForLoadStore(int level);
VkImage image = VK_NULL_HANDLE;
VkImageView imageView = VK_NULL_HANDLE;
@@ -124,6 +124,8 @@ struct QVkTexture : public QRhiTexture
VkFormat vkformat;
uint mipLevelCount = 0;
VkSampleCountFlagBits samples;
+ VkFormat viewFormat;
+ VkFormat viewFormatForSampling;
int lastActiveFrameSlot = -1;
uint generation = 0;
friend class QRhiVulkan;
@@ -162,7 +164,10 @@ struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor
QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
QVarLengthArray<VkSubpassDependency, 2> subpassDeps;
bool hasDepthStencil = false;
+ bool hasDepthStencilResolve = false;
+ uint32_t multiViewCount = 0;
VkAttachmentReference dsRef;
+ VkAttachmentReference dsResolveRef;
QVector<quint32> serializedFormatData;
QRhiVulkanRenderPassNativeHandles nativeHandlesStruct;
int lastActiveFrameSlot = -1;
@@ -178,6 +183,8 @@ struct QVkRenderTargetData
int colorAttCount = 0;
int dsAttCount = 0;
int resolveAttCount = 0;
+ int dsResolveAttCount = 0;
+ int multiViewCount = 0;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
static const int MAX_COLOR_ATTACHMENTS = 8;
};
@@ -210,7 +217,9 @@ struct QVkTextureRenderTarget : public QRhiTextureRenderTarget
QVkRenderTargetData d;
VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView dsv = VK_NULL_HANDLE;
VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resdsv = VK_NULL_HANDLE;
int lastActiveFrameSlot = -1;
friend class QRhiVulkan;
};
@@ -570,6 +579,7 @@ struct QVkSwapChain : public QRhiSwapChain
QRhiCommandBuffer *currentFrameCommandBuffer() override;
QRhiRenderTarget *currentFrameRenderTarget() override;
+ QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
QSize surfacePixelSize() override;
bool isFormatSupported(Format f) override;
@@ -584,6 +594,7 @@ struct QVkSwapChain : public QRhiSwapChain
QWindow *window = nullptr;
QSize pixelSize;
bool supportsReadback = false;
+ bool stereo = false;
VkSwapchainKHR sc = VK_NULL_HANDLE;
int bufferCount = 0;
VkSurfaceKHR surface = VK_NULL_HANDLE;
@@ -595,6 +606,7 @@ struct QVkSwapChain : public QRhiSwapChain
QVarLengthArray<VkPresentModeKHR, 8> supportedPresentationModes;
VkDeviceMemory msaaImageMem = VK_NULL_HANDLE;
QVkSwapChainRenderTarget rtWrapper;
+ QVkSwapChainRenderTarget rtWrapperRight;
QVkCommandBuffer cbWrapper;
struct ImageResources {
@@ -755,18 +767,20 @@ public:
void releaseSwapChainResources(QRhiSwapChain *swapChain);
VkFormat optimalDepthStencilFormat();
- VkSampleCountFlagBits effectiveSampleCount(int sampleCount);
+ VkSampleCountFlagBits effectiveSampleCountBits(int sampleCount);
bool createDefaultRenderPass(QVkRenderPassDescriptor *rpD,
bool hasDepthStencil,
VkSampleCountFlagBits samples,
VkFormat colorFormat);
bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
- const QRhiColorAttachment *firstColorAttachment,
- const QRhiColorAttachment *lastColorAttachment,
+ const QRhiColorAttachment *colorAttachmentsBegin,
+ const QRhiColorAttachment *colorAttachmentsEnd,
bool preserveColor,
bool preserveDs,
+ bool storeDs,
QRhiRenderBuffer *depthStencilBuffer,
- QRhiTexture *depthTexture);
+ QRhiTexture *depthTexture,
+ QRhiTexture *depthResolveTexture);
bool ensurePipelineCache(const void *initialData = nullptr, size_t initialDataSize = 0);
VkShaderModule createShader(const QByteArray &spirv);
@@ -816,6 +830,7 @@ public:
void updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx = -1);
void ensureCommandPoolForNewFrame();
double elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok);
+ void printExtraErrorInfo(VkResult err);
QVulkanInstance *inst = nullptr;
QWindow *maybeWindow = nullptr;
@@ -834,6 +849,16 @@ public:
QVulkanDeviceFunctions *df = nullptr;
QRhi::Flags rhiFlags;
VkPhysicalDeviceFeatures physDevFeatures;
+#ifdef VK_VERSION_1_1
+ VkPhysicalDeviceMultiviewFeatures multiviewFeaturesIfApi11;
+#endif
+#ifdef VK_VERSION_1_2
+ VkPhysicalDeviceVulkan11Features physDevFeatures11IfApi12OrNewer;
+ VkPhysicalDeviceVulkan12Features physDevFeatures12;
+#endif
+#ifdef VK_VERSION_1_3
+ VkPhysicalDeviceVulkan13Features physDevFeatures13;
+#endif
VkPhysicalDeviceProperties physDevProperties;
VkDeviceSize ubufAlign;
VkDeviceSize texbufAlign;
@@ -856,6 +881,10 @@ public:
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
+#ifdef VK_KHR_create_renderpass2
+ PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR = nullptr;
+#endif
+
struct {
bool compute = false;
bool wideLines = false;
@@ -865,6 +894,9 @@ public:
bool tessellation = false;
bool geometryShader = false;
bool nonFillPolygonMode = false;
+ bool multiView = false;
+ bool renderPass2KHR = false;
+ bool depthStencilResolveKHR = false;
QVersionNumber apiVersion;
} caps;
@@ -979,6 +1011,8 @@ public:
VkFramebuffer fb;
VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView dsv;
+ VkImageView resdsv;
} textureRenderTarget;
struct {
VkRenderPass rp;
diff --git a/src/gui/rhi/qrhivulkanext_p.h b/src/gui/rhi/qrhivulkanext_p.h
deleted file mode 100644
index 02b346948b..0000000000
--- a/src/gui/rhi/qrhivulkanext_p.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#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
index af2275f088..d5fb53e7e6 100644
--- a/src/gui/rhi/qshader.cpp
+++ b/src/gui/rhi/qshader.cpp
@@ -141,7 +141,7 @@ QT_BEGIN_NAMESPACE
A default constructed QShaderVersion contains a version of 100 and no
flags set.
- \note This a RHI API with limited compatibility guarantees, see \l QShader
+ \note This is a RHI API with limited compatibility guarantees, see \l QShader
for details.
*/
@@ -163,7 +163,7 @@ QT_BEGIN_NAMESPACE
A default constructed QShaderKey has source set to SpirvShader and
sourceVersion set to 100. sourceVariant defaults to StandardShader.
- \note This a RHI API with limited compatibility guarantees, see \l QShader
+ \note This is a RHI API with limited compatibility guarantees, see \l QShader
for details.
*/
@@ -214,6 +214,28 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \enum QShader::SerializedFormatVersion
+ Describes the desired output format when serializing the QShader.
+
+ The default value for the \c version argument of serialized() is \c Latest.
+ This is sufficient in the vast majority of cases. Specifying another value
+ is needed only when the intention is to generate serialized data that can
+ be loaded by earlier Qt versions. For example, the \c qsb tool uses these
+ enum values when the \c{--qsbversion} command-line argument is given.
+
+ \note Targeting earlier versions will make certain features disfunctional
+ with the generated asset. This is not an issue when using the asset with
+ the specified, older Qt version, given that that Qt version does not have
+ the newer features in newer Qt versions that rely on additional data
+ generated in the QShader and the serialized data stream, but may become a
+ problem if the generated asset is then used with a newer Qt version.
+
+ \value Latest The current Qt version
+ \value Qt_6_5 Qt 6.5
+ \value Qt_6_4 Qt 6.4
+ */
+
+/*!
\class QShaderCode
\inmodule QtGui
\since 6.6
@@ -223,7 +245,7 @@ QT_BEGIN_NAMESPACE
When shader() is empty after retrieving a QShaderCode instance from
QShader, it indicates no shader code was found for the requested key.
- \note This a RHI API with limited compatibility guarantees, see \l QShader
+ \note This is a RHI API with limited compatibility guarantees, see \l QShader
for details.
*/
@@ -277,6 +299,28 @@ QShader &QShader::operator=(const QShader &other)
}
/*!
+ \fn QShader::QShader(QShader &&other) noexcept
+ \since 6.7
+
+ Move-constructs a new QShader from \a other.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ \fn QShader &QShader::operator=(QShader &&other)
+ \since 6.7
+
+ Move-assigns \a other to this QShader instance.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
Destructor.
*/
QShader::~QShader()
@@ -286,6 +330,14 @@ QShader::~QShader()
}
/*!
+ \fn void QShader::swap(QShader &other)
+ \since 6.7
+
+ Swaps shader \a other with this shader. This operation is very fast and
+ never fails.
+*/
+
+/*!
\return true if the QShader contains at least one shader version.
*/
bool QShader::isValid() const
@@ -387,7 +439,11 @@ static void writeShaderKey(QDataStream *ds, const QShaderKey &k)
QShader, suitable for writing to files or other I/O devices.
By default the latest serialization format is used. Use \a version
- parameter to serialize for a compatibility Qt version.
+ parameter to serialize for a compatibility Qt version. Only when it is
+ known that the generated data stream must be made compatible with an older
+ Qt version at the expense of making it incompatible with features
+ introduced since that Qt version, should another value (for example,
+ \l{SerializedFormatVersion}{Qt_6_5} for Qt 6.5) be used.
\sa fromSerialized()
*/
@@ -476,6 +532,9 @@ static void readShaderKey(QDataStream *ds, QShaderKey *k)
/*!
Creates a new QShader instance from the given \a data.
+ If \a data cannot be deserialized successfully, the result is a default
+ constructed QShader for which isValid() returns \c false.
+
\sa serialized()
*/
QShader QShader::fromSerialized(const QByteArray &data)
@@ -748,7 +807,7 @@ size_t qHash(const QShader &s, size_t seed) noexcept
seed = hash(seed, s.stage());
if (!s.d->shaders.isEmpty()) {
seed = hash(seed, s.d->shaders.firstKey());
- seed = hash(seed, s.d->shaders.first());
+ seed = hash(seed, std::as_const(s.d->shaders).first());
}
}
return seed;
@@ -1021,7 +1080,7 @@ void QShader::removeResourceBindingMap(const QShaderKey &key)
\c{_54} which corresponds to two separate resource bindings (\c 1 and \c 2)
in the original shader.
- \note This a RHI API with limited compatibility guarantees, see \l QShader
+ \note This is a RHI API with limited compatibility guarantees, see \l QShader
for details.
*/
@@ -1103,7 +1162,7 @@ void QShader::removeSeparateToCombinedImageSamplerMappingList(const QShaderKey &
that the shader code relies on such a buffer present can be indicated by
the data in this struct.
- \note This a RHI API with limited compatibility guarantees, see \l QShader
+ \note This is a RHI API with limited compatibility guarantees, see \l QShader
for details.
*/
diff --git a/src/gui/rhi/qshader.h b/src/gui/rhi/qshader.h
index 0b52022596..2465081366 100644
--- a/src/gui/rhi/qshader.h
+++ b/src/gui/rhi/qshader.h
@@ -117,7 +117,11 @@ public:
QShader();
QShader(const QShader &other);
QShader &operator=(const QShader &other);
+ QShader(QShader &&other) noexcept : d(std::exchange(other.d, nullptr)) {}
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QShader)
~QShader();
+
+ void swap(QShader &other) noexcept { qt_ptr_swap(d, other.d); }
void detach();
bool isValid() const;
diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h
index a2df33d96b..f77bcb1259 100644
--- a/src/gui/rhi/qshader_p.h
+++ b/src/gui/rhi/qshader_p.h
@@ -41,7 +41,8 @@ struct Q_GUI_EXPORT QShaderPrivate
MslTessTescPatchOutputBufferBinding,
MslTessTescParamsBufferBinding,
MslTessTescInputBufferBinding,
- MslBufferSizeBufferBinding
+ MslBufferSizeBufferBinding,
+ MslMultiViewMaskBufferBinding
};
QShaderPrivate()
diff --git a/src/gui/rhi/qshaderdescription.cpp b/src/gui/rhi/qshaderdescription.cpp
index 97b8798034..f64daf02ef 100644
--- a/src/gui/rhi/qshaderdescription.cpp
+++ b/src/gui/rhi/qshaderdescription.cpp
@@ -287,7 +287,7 @@ QT_BEGIN_NAMESPACE
\brief Describes an input or output variable in the shader.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
@@ -338,7 +338,7 @@ QT_BEGIN_NAMESPACE
\brief Describes a member of a uniform or push constant block.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
@@ -390,7 +390,7 @@ QT_BEGIN_NAMESPACE
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.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
@@ -425,7 +425,7 @@ QT_BEGIN_NAMESPACE
\brief Describes a push constant block.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
@@ -448,7 +448,7 @@ QT_BEGIN_NAMESPACE
\brief Describes a shader storage block.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
@@ -491,7 +491,7 @@ QT_BEGIN_NAMESPACE
\brief Describes a built-in variable.
- \note This a RHI API with limited compatibility guarantees, see \l QShaderDescription
+ \note This is a RHI API with limited compatibility guarantees, see \l QShaderDescription
for details.
*/
diff --git a/src/gui/text/coretext/qcoretextfontdatabase.mm b/src/gui/text/coretext/qcoretextfontdatabase.mm
index 61c662cd45..19f3a2b335 100644
--- a/src/gui/text/coretext/qcoretextfontdatabase.mm
+++ b/src/gui/text/coretext/qcoretextfontdatabase.mm
@@ -361,7 +361,7 @@ static void getFontDescription(CTFontDescriptorRef font, FontDescription *fd)
fd->fixedPitch = false;
if (QCFType<CTFontRef> tempFont = CTFontCreateWithFontDescriptor(font, 0.0, 0)) {
- uint tag = MAKE_TAG('O', 'S', '/', '2');
+ uint tag = QFont::Tag("OS/2").value();
CTFontRef tempFontRef = tempFont;
void *userData = reinterpret_cast<void *>(&tempFontRef);
uint length = 128;
@@ -489,6 +489,31 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>::fontEngine
qreal scaledPointSize = fontDef.pixelSize;
CGAffineTransform matrix = qt_transform_from_fontdef(fontDef);
+
+ if (!fontDef.variableAxisValues.isEmpty()) {
+ QCFType<CFMutableDictionaryRef> variations = CFDictionaryCreateMutable(nullptr,
+ fontDef.variableAxisValues.size(),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ for (auto it = fontDef.variableAxisValues.constBegin();
+ it != fontDef.variableAxisValues.constEnd();
+ ++it) {
+ const quint32 tag = it.key().value();
+ const float value = it.value();
+ QCFType<CFNumberRef> tagRef = CFNumberCreate(nullptr, kCFNumberIntType, &tag);
+ QCFType<CFNumberRef> valueRef = CFNumberCreate(nullptr, kCFNumberFloatType, &value);
+
+ CFDictionarySetValue(variations, tagRef, valueRef);
+ }
+ QCFType<CFDictionaryRef> attributes = CFDictionaryCreate(nullptr,
+ (const void **) &kCTFontVariationAttribute,
+ (const void **) &variations,
+ 1,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, attributes);
+ }
+
if (QCFType<CTFontRef> font = CTFontCreateWithFontDescriptor(descriptor, scaledPointSize, &matrix))
return new QCoreTextFontEngine(font, fontDef);
@@ -504,7 +529,7 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const
if (NSValue *fontDataValue = descriptorAttribute<NSValue>(descriptor, (CFStringRef)kQtFontDataAttribute)) {
QByteArray *fontData = static_cast<QByteArray *>(fontDataValue.pointerValue);
return QFontEngineFT::create(*fontData, fontDef.pixelSize,
- static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
+ static_cast<QFont::HintingPreference>(fontDef.hintingPreference), fontDef.variableAxisValues);
} else if (NSURL *url = descriptorAttribute<NSURL>(descriptor, kCTFontURLAttribute)) {
QFontEngine::FaceId faceId;
@@ -515,6 +540,8 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const
QString styleName = QCFString(CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute));
faceId.index = QFreetypeFace::getFaceIndexByStyleName(faceFileName, styleName);
+ faceId.variableAxes = fontDef.variableAxisValues;
+
return QFontEngineFT::create(fontDef, faceId);
}
// We end up here with a descriptor does not contain Qt font data or kCTFontURLAttribute.
@@ -527,7 +554,7 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory<QFontEngineFT>::fontEngine(const
template <class T>
QFontEngine *QCoreTextFontDatabaseEngineFactory<T>::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
{
- return T::create(fontData, pixelSize, hintingPreference);
+ return T::create(fontData, pixelSize, hintingPreference, {});
}
// Explicitly instantiate so that we don't need the plugin to involve FreeType
@@ -545,7 +572,7 @@ CFArrayRef fallbacksForDescriptor(CTFontDescriptorRef descriptor)
}
CFArrayRef cascadeList = CFArrayRef(CTFontCopyDefaultCascadeListForLanguages(font,
- (CFArrayRef)[NSUserDefaults.standardUserDefaults stringArrayForKey:@"AppleLanguages"]));
+ (CFArrayRef)NSLocale.preferredLanguages));
if (!cascadeList) {
qCWarning(lcQpaFonts) << "Failed to create fallback cascade list for" << descriptor;
@@ -714,13 +741,20 @@ QStringList QCoreTextFontDatabase::addApplicationFont(const QByteArray &fontData
if (!fontData.isEmpty()) {
QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
- if (QCFType<CTFontDescriptorRef> descriptor = CTFontManagerCreateFontDescriptorFromData(fontDataReference)) {
- // There's no way to get the data back out of a font descriptor created with
- // CTFontManagerCreateFontDescriptorFromData, so we attach the data manually.
- NSDictionary *attributes = @{ kQtFontDataAttribute : [NSValue valueWithPointer:new QByteArray(fontData)] };
- descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
+ if (QCFType<CFArrayRef> descriptors = CTFontManagerCreateFontDescriptorsFromData(fontDataReference)) {
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
- CFArrayAppendValue(array, descriptor);
+ const int count = CFArrayGetCount(descriptors);
+
+ for (int i = 0; i < count; ++i) {
+ CTFontDescriptorRef descriptor = CTFontDescriptorRef(CFArrayGetValueAtIndex(descriptors, i));
+
+ // There's no way to get the data back out of a font descriptor created with
+ // CTFontManagerCreateFontDescriptorFromData, so we attach the data manually.
+ NSDictionary *attributes = @{ kQtFontDataAttribute : [NSValue valueWithPointer:new QByteArray(fontData)] };
+ descriptor = CTFontDescriptorCreateCopyWithAttributes(descriptor, (CFDictionaryRef)attributes);
+ CFArrayAppendValue(array, descriptor);
+ }
+
fonts = array;
}
} else {
@@ -862,7 +896,7 @@ static CTFontDescriptorRef fontDescriptorFromTheme(QPlatformTheme::Font f)
UIFontDescriptor *desc = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
return static_cast<CTFontDescriptorRef>(CFBridgingRetain(desc));
}
-#endif // Q_OS_IOS, Q_OS_TVOS, Q_OS_WATCHOS
+#endif // QT_PLATFORM_UIKIT
// macOS default case and iOS fallback case
return descriptorForFontType(fontTypeFromTheme(f));
@@ -905,7 +939,7 @@ void QCoreTextFontDatabase::populateThemeFonts()
auto addFontVariants = [&](CTFontDescriptorRef descriptor) {
QCFType<CFArrayRef> matchingDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(descriptor, nullptr);
- const int matchingDescriptorsCount = CFArrayGetCount(matchingDescriptors);
+ const int matchingDescriptorsCount = matchingDescriptors ? CFArrayGetCount(matchingDescriptors) : 0;
qCDebug(lcQpaFonts) << "Enumerating font variants based on" << id(descriptor)
<< "resulted in" << matchingDescriptorsCount << "matching descriptors"
<< matchingDescriptors.as<NSArray*>();
@@ -980,5 +1014,10 @@ QList<int> QCoreTextFontDatabase::standardSizes() const
return ret;
}
+bool QCoreTextFontDatabase::supportsVariableApplicationFonts() const
+{
+ return true;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/coretext/qcoretextfontdatabase_p.h b/src/gui/text/coretext/qcoretextfontdatabase_p.h
index 74c3f30b79..eeea9ad640 100644
--- a/src/gui/text/coretext/qcoretextfontdatabase_p.h
+++ b/src/gui/text/coretext/qcoretextfontdatabase_p.h
@@ -46,6 +46,7 @@ public:
QFont defaultFont() const override;
bool fontsAlwaysScalable() const override;
QList<int> standardSizes() const override;
+ bool supportsVariableApplicationFonts() const override;
// For iOS and macOS platform themes
QFont *themeFont(QPlatformTheme::Font) const;
diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm
index 599a7f08c3..1050c03d75 100644
--- a/src/gui/text/coretext/qfontengine_coretext.mm
+++ b/src/gui/text/coretext/qfontengine_coretext.mm
@@ -12,6 +12,9 @@
#include <QtGui/qpainterpath.h>
#include <private/qcoregraphics_p.h>
#include <private/qimage_p.h>
+#include <private/qguiapplication_p.h>
+#include <private/qstringiterator_p.h>
+#include <qpa/qplatformtheme.h>
#include <cmath>
@@ -125,9 +128,13 @@ public:
QByteArray m_fontData;
};
-QCoreTextFontEngine *QCoreTextFontEngine::create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
+QCoreTextFontEngine *QCoreTextFontEngine::create(const QByteArray &fontData,
+ qreal pixelSize,
+ QFont::HintingPreference hintingPreference,
+ const QMap<QFont::Tag, float> &variableAxisValues)
{
Q_UNUSED(hintingPreference);
+ Q_UNUSED(variableAxisValues);
QCFType<CFDataRef> fontDataReference = fontData.toRawCFData();
QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithCFData(fontDataReference);
@@ -186,6 +193,7 @@ void QCoreTextFontEngine::init()
face_id.index = 0;
QCFString name = CTFontCopyName(ctfont, kCTFontUniqueNameKey);
face_id.filename = QString::fromCFString(name).toUtf8();
+ face_id.variableAxes = fontDef.variableAxisValues;
QCFString family = CTFontCopyFamilyName(ctfont);
fontDef.families = QStringList(family);
@@ -230,7 +238,7 @@ void QCoreTextFontEngine::init()
synthesisFlags |= SynthesizedItalic;
avgCharWidth = 0;
- QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
+ QByteArray os2Table = getSfntTable(QFont::Tag("OS/2").value());
unsigned emSize = CTFontGetUnitsPerEm(ctfont);
if (os2Table.size() >= 10) {
fsType = qFromBigEndian<quint16>(os2Table.constData() + 8);
@@ -267,29 +275,30 @@ glyph_t QCoreTextFontEngine::glyphIndex(uint ucs4) const
return glyphIndices[0];
}
-bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
- int *nglyphs, QFontEngine::ShaperFlags flags) const
+int QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
+ int *nglyphs, QFontEngine::ShaperFlags flags) const
{
Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
if (*nglyphs < len) {
*nglyphs = len;
- return false;
+ return -1;
}
QVarLengthArray<CGGlyph> cgGlyphs(len);
CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len);
int glyph_pos = 0;
- for (int i = 0; i < len; ++i) {
- glyphs->glyphs[glyph_pos] = cgGlyphs[i];
- if (glyph_pos < i)
- cgGlyphs[glyph_pos] = cgGlyphs[i];
- glyph_pos++;
-
- // If it's a non-BMP char, skip the lower part of surrogate pair and go
- // directly to the next char without increasing glyph_pos
- if (str[i].isHighSurrogate() && i < len-1 && str[i+1].isLowSurrogate())
- ++i;
+ int mappedGlyphs = 0;
+ QStringIterator it(str, str + len);
+ while (it.hasNext()) {
+ qsizetype idx = it.index();
+ char32_t ucs4 = it.next();
+ glyphs->glyphs[glyph_pos] = cgGlyphs[idx];
+ if (glyph_pos < idx)
+ cgGlyphs[glyph_pos] = cgGlyphs[idx];
+ if (glyphs->glyphs[glyph_pos] != 0 || isIgnorableChar(ucs4))
+ mappedGlyphs++;
+ glyph_pos++;
}
*nglyphs = glyph_pos;
@@ -298,7 +307,7 @@ bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *
if (!(flags & GlyphIndicesOnly))
loadAdvancesForGlyphs(cgGlyphs, glyphs);
- return true;
+ return mappedGlyphs;
}
glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph)
@@ -716,10 +725,12 @@ QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, const QFixedPoint &subP
// draw with white or black fill, and then invert the glyph image in the latter case,
// producing an alpha map. This covers the most common use-cases, but longer term we
// should propagate the fill color all the way from the paint engine, and include it
- //in the key for the glyph cache.
+ // in the key for the glyph cache.
- if (!qt_mac_applicationIsInDarkMode())
- return kCGColorBlack;
+ if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) {
+ if (platformTheme->colorScheme() != Qt::ColorScheme::Dark)
+ return kCGColorBlack;
+ }
}
return kCGColorWhite;
}();
diff --git a/src/gui/text/coretext/qfontengine_coretext_p.h b/src/gui/text/coretext/qfontengine_coretext_p.h
index 665b827f11..2f388c32bc 100644
--- a/src/gui/text/coretext/qfontengine_coretext_p.h
+++ b/src/gui/text/coretext/qfontengine_coretext_p.h
@@ -38,7 +38,7 @@ public:
~QCoreTextFontEngine();
glyph_t glyphIndex(uint ucs4) const override;
- bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
glyph_metrics_t boundingBox(glyph_t glyph) override;
@@ -91,7 +91,7 @@ public:
static bool ct_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length);
static QFont::Weight qtWeightFromCFWeight(float value);
- static QCoreTextFontEngine *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference);
+ static QCoreTextFontEngine *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference, const QMap<QFont::Tag, float> &variableAxisValue);
protected:
QCoreTextFontEngine(const QFontDef &def);
diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp
index 5b48ad979b..d3791f1f6e 100644
--- a/src/gui/text/freetype/qfontengine_ft.cpp
+++ b/src/gui/text/freetype/qfontengine_ft.cpp
@@ -12,6 +12,7 @@
#include <qscreen.h>
#include <qpa/qplatformscreen.h>
#include <QtCore/QUuid>
+#include <QtCore/QLoggingCategory>
#include <QtGui/QPainterPath>
#ifndef QT_NO_FREETYPE
@@ -34,6 +35,7 @@
#include FT_GLYPH_H
#include FT_MODULE_H
#include FT_LCD_FILTER_H
+#include FT_MULTIPLE_MASTERS_H
#if defined(FT_CONFIG_OPTIONS_H)
#include FT_CONFIG_OPTIONS_H
@@ -53,6 +55,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcFontMatch)
+
using namespace Qt::StringLiterals;
#define FLOOR(x) ((x) & -64)
@@ -111,8 +115,11 @@ public:
QtFreetypeData::~QtFreetypeData()
{
- for (QHash<QFontEngine::FaceId, QFreetypeFace *>::ConstIterator iter = faces.cbegin(); iter != faces.cend(); ++iter)
+ for (auto iter = faces.cbegin(); iter != faces.cend(); ++iter) {
iter.value()->cleanup();
+ if (!iter.value()->ref.deref())
+ delete iter.value();
+ }
faces.clear();
FT_Done_FreeType(library);
library = nullptr;
@@ -183,10 +190,15 @@ int QFreetypeFace::getPointInOutline(glyph_t glyph, int flags, quint32 point, QF
return Err_Ok;
}
+bool QFreetypeFace::isScalable() const
+{
+ return FT_IS_SCALABLE(face);
+}
+
bool QFreetypeFace::isScalableBitmap() const
{
#ifdef FT_HAS_COLOR
- return !FT_IS_SCALABLE(face) && FT_HAS_COLOR(face);
+ return !isScalable() && FT_HAS_COLOR(face);
#else
return false;
#endif
@@ -209,10 +221,28 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
QtFreetypeData *freetypeData = qt_getFreetypeData();
- QFreetypeFace *freetype = freetypeData->faces.value(face_id, nullptr);
- if (freetype) {
- freetype->ref.ref();
- } else {
+ QFreetypeFace *freetype = nullptr;
+ auto it = freetypeData->faces.find(face_id);
+ if (it != freetypeData->faces.end()) {
+ freetype = *it;
+
+ Q_ASSERT(freetype->ref.loadRelaxed() > 0);
+ if (freetype->ref.loadRelaxed() == 1) {
+ // If there is only one reference left to the face, it means it is only referenced by
+ // the cache itself, and thus it is in cleanup state (but the final outside reference
+ // was removed on a different thread so it could not be deleted right away). We then
+ // complete the cleanup and pretend we didn't find it, so that it can be re-created with
+ // the present state.
+ freetype->cleanup();
+ freetypeData->faces.erase(it);
+ delete freetype;
+ freetype = nullptr;
+ } else {
+ freetype->ref.ref();
+ }
+ }
+
+ if (!freetype) {
const auto deleter = [](QFreetypeFace *f) { delete f; };
std::unique_ptr<QFreetypeFace, decltype(deleter)> newFreetype(new QFreetypeFace, deleter);
FT_Face face;
@@ -243,7 +273,23 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
} else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) {
return nullptr;
}
+
+#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
+ if (face_id.instanceIndex >= 0) {
+ qCDebug(lcFontMatch)
+ << "Selecting named instance" << (face_id.instanceIndex)
+ << "in" << face_id.filename;
+ FT_Set_Named_Instance(face, face_id.instanceIndex + 1);
+ }
+#endif
newFreetype->face = face;
+ newFreetype->mm_var = nullptr;
+ if (FT_IS_NAMED_INSTANCE(newFreetype->face)) {
+ FT_Error ftresult;
+ ftresult = FT_Get_MM_Var(face, &newFreetype->mm_var);
+ if (ftresult != FT_Err_Ok)
+ newFreetype->mm_var = nullptr;
+ }
newFreetype->ref.storeRelaxed(1);
newFreetype->xsize = 0;
@@ -282,6 +328,25 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
FT_Set_Char_Size(face, newFreetype->face->available_sizes[0].x_ppem, newFreetype->face->available_sizes[0].y_ppem, 0, 0);
FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map);
+
+ if (!face_id.variableAxes.isEmpty()) {
+ FT_MM_Var *var = nullptr;
+ FT_Get_MM_Var(newFreetype->face, &var);
+ if (var != nullptr) {
+ QVarLengthArray<FT_Fixed, 16> coords(var->num_axis);
+ FT_Get_Var_Design_Coordinates(face, var->num_axis, coords.data());
+ for (FT_UInt i = 0; i < var->num_axis; ++i) {
+ if (const auto tag = QFont::Tag::fromValue(var->axis[i].tag)) {
+ const auto it = face_id.variableAxes.constFind(*tag);
+ if (it != face_id.variableAxes.constEnd())
+ coords[i] = FT_Fixed(*it * 65536);
+ }
+ }
+ FT_Set_Var_Design_Coordinates(face, var->num_axis, coords.data());
+ FT_Done_MM_Var(qt_getFreetype(), var);
+ }
+ }
+
QT_TRY {
freetypeData->faces.insert(face_id, newFreetype.get());
} QT_CATCH(...) {
@@ -290,6 +355,7 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
QT_RETHROW;
}
freetype = newFreetype.release();
+ freetype->ref.ref();
}
return freetype;
}
@@ -297,30 +363,45 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
void QFreetypeFace::cleanup()
{
hbFace.reset();
+ if (mm_var && face && face->glyph)
+ FT_Done_MM_Var(face->glyph->library, mm_var);
+ mm_var = nullptr;
FT_Done_Face(face);
face = nullptr;
}
void QFreetypeFace::release(const QFontEngine::FaceId &face_id)
{
- if (!ref.deref()) {
- if (face) {
- QtFreetypeData *freetypeData = qt_getFreetypeData();
-
- cleanup();
-
- auto it = freetypeData->faces.constFind(face_id);
- if (it != freetypeData->faces.constEnd())
- freetypeData->faces.erase(it);
-
- if (freetypeData->faces.isEmpty()) {
- FT_Done_FreeType(freetypeData->library);
- freetypeData->library = nullptr;
+ Q_UNUSED(face_id);
+ bool deleteThis = !ref.deref();
+
+ // If the only reference left over is the cache's reference, we remove it from the cache,
+ // granted that we are on the correct thread. If not, we leave it there to be cleaned out
+ // later. While we are at it, we also purge all left over faces which are only referenced
+ // from the cache.
+ if (face && ref.loadRelaxed() == 1) {
+ QtFreetypeData *freetypeData = qt_getFreetypeData();
+ for (auto it = freetypeData->faces.constBegin(); it != freetypeData->faces.constEnd(); ) {
+ if (it.value()->ref.loadRelaxed() == 1) {
+ it.value()->cleanup();
+ if (it.value() == this)
+ deleteThis = true; // This face, delete at end of function for safety
+ else
+ delete it.value();
+ it = freetypeData->faces.erase(it);
+ } else {
+ ++it;
}
}
- delete this;
+ if (freetypeData->faces.isEmpty()) {
+ FT_Done_FreeType(freetypeData->library);
+ freetypeData->library = nullptr;
+ }
}
+
+ if (deleteThis)
+ delete this;
}
static int computeFaceIndex(const QString &faceFileName, const QString &styleName)
@@ -339,12 +420,12 @@ static int computeFaceIndex(const QString &faceFileName, const QString &styleNam
break;
}
- QString faceStyleName = QString::fromLatin1(face->style_name);
+ const bool found = QLatin1StringView(face->style_name) == styleName;
numFaces = face->num_faces;
FT_Done_Face(face);
- if (faceStyleName == styleName)
+ if (found)
return faceIndex;
} while (++faceIndex < numFaces);
@@ -674,27 +755,32 @@ namespace {
fontDef.weight = QFont::Bold;
}
- bool initFromData(const QByteArray &fontData)
+ bool initFromData(const QByteArray &fontData, const QMap<QFont::Tag, float> &variableAxisValues)
{
FaceId faceId;
faceId.filename = "";
faceId.index = 0;
faceId.uuid = QUuid::createUuid().toByteArray();
+ faceId.variableAxes = variableAxisValues;
return init(faceId, true, Format_None, fontData);
}
};
}
-QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
+QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData,
+ qreal pixelSize,
+ QFont::HintingPreference hintingPreference,
+ const QMap<QFont::Tag, float> &variableAxisValues)
{
QFontDef fontDef;
fontDef.pixelSize = pixelSize;
fontDef.stretch = QFont::Unstretched;
fontDef.hintingPreference = hintingPreference;
+ fontDef.variableAxisValues = variableAxisValues;
QFontEngineFTRawData *fe = new QFontEngineFTRawData(fontDef);
- if (!fe->initFromData(fontData)) {
+ if (!fe->initFromData(fontData, variableAxisValues)) {
delete fe;
return nullptr;
}
@@ -747,6 +833,37 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
static void dont_delete(void*) {}
+static FT_UShort calculateActualWeight(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
+{
+ FT_MM_Var *var = freetypeFace->mm_var;
+ if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) {
+ for (FT_UInt axis = 0; axis < var->num_axis; ++axis) {
+ if (var->axis[axis].tag == QFont::Tag("wght").value()) {
+ return var->namedstyle[faceId.instanceIndex].coords[axis] >> 16;
+ }
+ }
+ }
+ if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) {
+ return os2->usWeightClass;
+ }
+
+ return 700;
+}
+
+static bool calculateActualItalic(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
+{
+ FT_MM_Var *var = freetypeFace->mm_var;
+ if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) {
+ for (FT_UInt axis = 0; axis < var->num_axis; ++axis) {
+ if (var->axis[axis].tag == QFont::Tag("ital").value()) {
+ return (var->namedstyle[faceId.instanceIndex].coords[axis] >> 16) == 1;
+ }
+ }
+ }
+
+ return (face->style_flags & FT_STYLE_FLAG_ITALIC);
+}
+
bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
QFreetypeFace *freetypeFace)
{
@@ -770,7 +887,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
PS_FontInfoRec psrec;
// don't assume that type1 fonts are symbol fonts by default
if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) {
- symbol = !fontDef.families.isEmpty() && bool(fontDef.families.first().contains("symbol"_L1, Qt::CaseInsensitive));
+ symbol = !fontDef.families.isEmpty() && bool(fontDef.families.constFirst().contains("symbol"_L1, Qt::CaseInsensitive));
}
freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing, &scalableBitmapScaleFactor);
@@ -778,18 +895,18 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
FT_Face face = lockFace();
if (FT_IS_SCALABLE(face)) {
- bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !(face->style_flags & FT_STYLE_FLAG_ITALIC) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC");
+ bool isItalic = calculateActualItalic(freetype, face, faceId);
+ bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !isItalic && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC");
if (fake_oblique)
obliquen = true;
FT_Set_Transform(face, &matrix, nullptr);
freetype->matrix = matrix;
// fake bold
if ((fontDef.weight >= QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD")) {
- if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) {
- if (os2->usWeightClass < 700 &&
- (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) {
- embolden = true;
- }
+ FT_UShort actualWeight = calculateActualWeight(freetype, face, faceId);
+ if (actualWeight < 700 &&
+ (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) {
+ embolden = true;
}
}
// underline metrics
@@ -1253,7 +1370,7 @@ QFontEngine::Properties QFontEngineFT::properties() const
{
Properties p = freetype->properties();
if (p.postscriptName.isEmpty()) {
- p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.families.first().toUtf8());
+ p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.families.constFirst().toUtf8());
}
return freetype->properties();
@@ -1480,7 +1597,7 @@ void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_me
bool QFontEngineFT::supportsTransformation(const QTransform &transform) const
{
- return transform.type() <= QTransform::TxRotate;
+ return transform.type() <= QTransform::TxRotate && (freetype->isScalable() || freetype->isScalableBitmap());
}
void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags)
@@ -1561,15 +1678,16 @@ glyph_t QFontEngineFT::glyphIndex(uint ucs4) const
return glyph;
}
-bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
+int QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
QFontEngine::ShaperFlags flags) const
{
Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
if (*nglyphs < len) {
*nglyphs = len;
- return false;
+ return -1;
}
+ int mappedGlyphs = 0;
int glyph_pos = 0;
if (freetype->symbol_map) {
FT_Face face = freetype->face;
@@ -1602,6 +1720,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs
if (uc < QFreetypeFace::cmapCacheSize)
freetype->cmapCache[uc] = glyph;
}
+ if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
+ mappedGlyphs++;
++glyph_pos;
}
} else {
@@ -1623,6 +1743,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs
freetype->cmapCache[uc] = glyph;
}
}
+ if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
+ mappedGlyphs++;
++glyph_pos;
}
}
@@ -1633,7 +1755,7 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs
if (!(flags & GlyphIndicesOnly))
recalcAdvances(glyphs, flags);
- return true;
+ return mappedGlyphs;
}
bool QFontEngineFT::shouldUseDesignMetrics(QFontEngine::ShaperFlags flags) const
@@ -1828,7 +1950,8 @@ glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph,
// outline drawing. To ensure the bounding box matches the rendered glyph, we
// need to do the same here.
- const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) && !matrix.isIdentity();
+ const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face)
+ && matrix.type() > QTransform::TxTranslate;
if (needsImageTransform && format == QFontEngine::Format_Mono)
format = QFontEngine::Format_A8;
Glyph *g = loadGlyphFor(glyph, subPixelPosition, format, matrix, true, true);
@@ -1957,7 +2080,8 @@ QImage QFontEngineFT::alphaMapForGlyph(glyph_t g,
const QFixedPoint &subPixelPosition,
const QTransform &t)
{
- const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) && !t.isIdentity();
+ const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face)
+ && t.type() > QTransform::TxTranslate;
const GlyphFormat neededFormat = antialias || needsImageTransform ? Format_A8 : Format_Mono;
Glyph *glyph = loadGlyphFor(g, subPixelPosition, neededFormat, t, false, true);
diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h
index e195174960..bdd4549827 100644
--- a/src/gui/text/freetype/qfontengine_ft_p.h
+++ b/src/gui/text/freetype/qfontengine_ft_p.h
@@ -19,6 +19,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
+#include FT_MULTIPLE_MASTERS_H
#ifndef Q_OS_WIN
@@ -62,6 +63,7 @@ public:
}
FT_Face face;
+ FT_MM_Var *mm_var;
int xsize; // 26.6
int ysize; // 26.6
FT_Matrix matrix;
@@ -75,6 +77,7 @@ public:
int getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints);
+ bool isScalable() const;
bool isScalableBitmap() const;
static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale);
@@ -183,7 +186,7 @@ private:
void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs,
QPainterPath *path, QTextItem::RenderFlags flags) override;
- bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) override;
glyph_metrics_t boundingBox(glyph_t glyph) override;
@@ -266,7 +269,7 @@ private:
HintStyle defaultHintStyle() const { return default_hint_style; }
static QFontEngineFT *create(const QFontDef &fontDef, FaceId faceId, const QByteArray &fontData = QByteArray());
- static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference);
+ static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference, const QMap<QFont::Tag, float> &variableAxisValue);
protected:
diff --git a/src/gui/text/freetype/qfreetypefontdatabase.cpp b/src/gui/text/freetype/qfreetypefontdatabase.cpp
index cf1ca42ab4..018e590ac2 100644
--- a/src/gui/text/freetype/qfreetypefontdatabase.cpp
+++ b/src/gui/text/freetype/qfreetypefontdatabase.cpp
@@ -10,6 +10,8 @@
#include <QtCore/QLibraryInfo>
#include <QtCore/QDir>
#include <QtCore/QtEndian>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QUuid>
#undef QT_NO_FREETYPE
#include "qfontengine_ft_p.h"
@@ -18,8 +20,14 @@
#include FT_TRUETYPE_TABLES_H
#include FT_ERRORS_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_SFNT_NAMES_H
+#include FT_TRUETYPE_IDS_H
+
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcFontDb)
+
using namespace Qt::StringLiterals;
void QFreeTypeFontDatabase::populateFontDatabase()
@@ -54,6 +62,16 @@ QFontEngine *QFreeTypeFontDatabase::fontEngine(const QFontDef &fontDef, void *us
QFontEngine::FaceId faceId;
faceId.filename = QFile::encodeName(fontfile->fileName);
faceId.index = fontfile->indexValue;
+ faceId.instanceIndex = fontfile->instanceIndex;
+ faceId.variableAxes = fontDef.variableAxisValues;
+
+ // Make sure the FaceId compares uniquely in cases where a
+ // file name is not provided.
+ if (faceId.filename.isEmpty()) {
+ QUuid::Id128Bytes id{};
+ memcpy(&id, &usrPtr, sizeof(usrPtr));
+ faceId.uuid = QUuid(id).toByteArray();
+ }
return QFontEngineFT::create(fontDef, faceId, fontfile->data);
}
@@ -61,7 +79,7 @@ QFontEngine *QFreeTypeFontDatabase::fontEngine(const QFontDef &fontDef, void *us
QFontEngine *QFreeTypeFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize,
QFont::HintingPreference hintingPreference)
{
- return QFontEngineFT::create(fontData, pixelSize, hintingPreference);
+ return QFontEngineFT::create(fontData, pixelSize, hintingPreference, {});
}
QStringList QFreeTypeFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont)
@@ -77,6 +95,114 @@ void QFreeTypeFontDatabase::releaseHandle(void *handle)
extern FT_Library qt_getFreetype();
+void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_,
+ int faceIndex,
+ const QString &family,
+ const QString &styleName,
+ QFont::Weight weight,
+ QFont::Stretch stretch,
+ QFont::Style style,
+ bool fixedPitch,
+ const QSupportedWritingSystems &writingSystems,
+ const QByteArray &fileName,
+ const QByteArray &fontData)
+{
+ FT_Face face = reinterpret_cast<FT_Face>(face_);
+
+ // Note: The following does not actually depend on API from 2.9, but the
+ // FT_Set_Named_Instance() was added in 2.9, so to avoid populating the database with
+ // named instances that cannot be selected, we disable the feature on older Freetype
+ // versions.
+#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
+ FT_MM_Var *var = nullptr;
+ FT_Get_MM_Var(face, &var);
+ if (var != nullptr) {
+ std::unique_ptr<FT_MM_Var, void(*)(FT_MM_Var*)> varGuard(var, [](FT_MM_Var *res) {
+ FT_Done_MM_Var(qt_getFreetype(), res);
+ });
+
+ for (FT_UInt i = 0; i < var->num_namedstyles; ++i) {
+ FT_UInt id = var->namedstyle[i].strid;
+
+ QFont::Weight instanceWeight = weight;
+ QFont::Stretch instanceStretch = stretch;
+ QFont::Style instanceStyle = style;
+ for (FT_UInt axis = 0; axis < var->num_axis; ++axis) {
+ if (var->axis[axis].tag == QFont::Tag("wght").value()) {
+ instanceWeight = QFont::Weight(var->namedstyle[i].coords[axis] >> 16);
+ } else if (var->axis[axis].tag == QFont::Tag("wdth").value()) {
+ instanceStretch = QFont::Stretch(var->namedstyle[i].coords[axis] >> 16);
+ } else if (var->axis[axis].tag == QFont::Tag("ital").value()) {
+ FT_UInt ital = var->namedstyle[i].coords[axis] >> 16;
+ if (ital == 1)
+ instanceStyle = QFont::StyleItalic;
+ else
+ instanceStyle = QFont::StyleNormal;
+ }
+ }
+
+ FT_UInt count = FT_Get_Sfnt_Name_Count(face);
+ for (FT_UInt j = 0; j < count; ++j) {
+ FT_SfntName name;
+ if (FT_Get_Sfnt_Name(face, j, &name))
+ continue;
+
+ if (name.name_id != id)
+ continue;
+
+ // Only support Unicode for now
+ if (name.encoding_id != TT_MS_ID_UNICODE_CS)
+ continue;
+
+ // Sfnt names stored as UTF-16BE
+ QString instanceName;
+ for (FT_UInt k = 0; k < name.string_len; k += 2)
+ instanceName += QChar((name.string[k] << 8) + name.string[k + 1]);
+ if (instanceName != styleName) {
+ FontFile *variantFontFile = new FontFile{
+ QFile::decodeName(fileName),
+ faceIndex,
+ int(i),
+ fontData
+ };
+
+ qCDebug(lcFontDb) << "Registering named instance" << i
+ << ":" << instanceName
+ << "for font family" << family
+ << "with weight" << instanceWeight
+ << ", style" << instanceStyle
+ << ", stretch" << instanceStretch;
+
+ registerFont(family,
+ instanceName,
+ QString(),
+ instanceWeight,
+ instanceStyle,
+ instanceStretch,
+ true,
+ true,
+ 0,
+ fixedPitch,
+ writingSystems,
+ variantFontFile);
+ }
+ }
+ }
+ }
+#else
+ Q_UNUSED(face);
+ Q_UNUSED(family);
+ Q_UNUSED(styleName);
+ Q_UNUSED(weight);
+ Q_UNUSED(stretch);
+ Q_UNUSED(style);
+ Q_UNUSED(fixedPitch);
+ Q_UNUSED(writingSystems);
+ Q_UNUSED(fontData);
+#endif
+
+}
+
QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont)
{
FT_Library library = qt_getFreetype();
@@ -194,6 +320,7 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q
FontFile *fontFile = new FontFile{
QFile::decodeName(file),
index,
+ -1,
fontData
};
@@ -211,6 +338,9 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q
}
registerFont(family, styleName, QString(), weight, style, stretch, true, true, 0, fixedPitch, writingSystems, fontFile);
+
+ addNamedInstancesForFace(face, index, family, styleName, weight, stretch, style, fixedPitch, writingSystems, file, fontData);
+
families.append(family);
FT_Done_Face(face);
@@ -219,4 +349,13 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q
return families;
}
+bool QFreeTypeFontDatabase::supportsVariableApplicationFonts() const
+{
+#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
+ return true;
+#else
+ return false;
+#endif
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/freetype/qfreetypefontdatabase_p.h b/src/gui/text/freetype/qfreetypefontdatabase_p.h
index ffb10a2e5c..5fcec585d2 100644
--- a/src/gui/text/freetype/qfreetypefontdatabase_p.h
+++ b/src/gui/text/freetype/qfreetypefontdatabase_p.h
@@ -26,6 +26,7 @@ struct FontFile
{
QString fileName;
int indexValue;
+ int instanceIndex = -1;
// Note: The data may be implicitly shared throughout the
// font database and platform font database, so be careful
@@ -41,6 +42,14 @@ public:
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override;
void releaseHandle(void *handle) override;
+ bool supportsVariableApplicationFonts() const override;
+
+ static void addNamedInstancesForFace(void *face, int faceIndex,
+ const QString &family, const QString &styleName,
+ QFont::Weight weight, QFont::Stretch stretch,
+ QFont::Style style, bool fixedPitch,
+ const QSupportedWritingSystems &writingSystems,
+ const QByteArray &fileName, const QByteArray &fontData);
static QStringList addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr);
};
diff --git a/src/gui/text/qabstracttextdocumentlayout.cpp b/src/gui/text/qabstracttextdocumentlayout.cpp
index 9c75031e32..3e5144b157 100644
--- a/src/gui/text/qabstracttextdocumentlayout.cpp
+++ b/src/gui/text/qabstracttextdocumentlayout.cpp
@@ -101,7 +101,7 @@ QTextObjectInterface::~QTextObjectInterface()
\warning Copy and Paste operations ignore custom text objects.
- \sa {Text Object Example}, QTextCharFormat, QTextLayout
+ \sa QTextCharFormat, QTextLayout
*/
/*!
diff --git a/src/gui/text/qabstracttextdocumentlayout_p.h b/src/gui/text/qabstracttextdocumentlayout_p.h
index f10c1d9bd2..6bd42d78d8 100644
--- a/src/gui/text/qabstracttextdocumentlayout_p.h
+++ b/src/gui/text/qabstracttextdocumentlayout_p.h
@@ -19,7 +19,9 @@
#include "private/qobject_p.h"
#include "qtextdocument_p.h"
#include "qabstracttextdocumentlayout.h"
+
#include "QtCore/qhash.h"
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp
index 02d99a82ed..7886d9ba91 100644
--- a/src/gui/text/qcssparser.cpp
+++ b/src/gui/text/qcssparser.cpp
@@ -12,6 +12,7 @@
#include <qfontmetrics.h>
#include <qbrush.h>
#include <qimagereader.h>
+#include <qtextformat.h>
#include <algorithm>
@@ -35,19 +36,28 @@ struct QCssKnownValue
quint64 id;
};
+// This array is sorted alphabetically.
static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
{ "-qt-fg-texture-cachekey", QtForegroundTextureCacheKey },
+ { "-qt-foreground", QtForeground },
{ "-qt-line-height-type", QtLineHeightType },
{ "-qt-list-indent", QtListIndent },
{ "-qt-list-number-prefix", QtListNumberPrefix },
{ "-qt-list-number-suffix", QtListNumberSuffix },
{ "-qt-paragraph-type", QtParagraphType },
+ { "-qt-stroke-color", QtStrokeColor },
+ { "-qt-stroke-dasharray", QtStrokeDashArray },
+ { "-qt-stroke-dashoffset", QtStrokeDashOffset },
+ { "-qt-stroke-linecap", QtStrokeLineCap },
+ { "-qt-stroke-linejoin", QtStrokeLineJoin },
+ { "-qt-stroke-miterlimit", QtStrokeMiterLimit },
+ { "-qt-stroke-width", QtStrokeWidth },
{ "-qt-style-features", QtStyleFeatures },
{ "-qt-table-type", QtTableType },
{ "-qt-user-state", QtUserState },
- { "accent-color", QtAccentColor },
+ { "accent-color", QtAccent },
{ "alternate-background-color", QtAlternateBackground },
{ "background", Background },
{ "background-attachment", BackgroundAttachment },
@@ -156,6 +166,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "always", Value_Always },
{ "auto", Value_Auto },
{ "base", Value_Base },
+ { "beveljoin", Value_BevelJoin},
{ "bold", Value_Bold },
{ "bottom", Value_Bottom },
{ "bright-text", Value_BrightText },
@@ -172,6 +183,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "dot-dot-dash", Value_DotDotDash },
{ "dotted", Value_Dotted },
{ "double", Value_Double },
+ { "flatcap", Value_FlatCap},
{ "groove", Value_Groove },
{ "highlight", Value_Highlight },
{ "highlighted-text", Value_HighlightedText },
@@ -190,6 +202,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "mid", Value_Mid },
{ "middle", Value_Middle },
{ "midlight", Value_Midlight },
+ { "miterjoin", Value_MiterJoin},
{ "native", Value_Native },
{ "none", Value_None },
{ "normal", Value_Normal },
@@ -204,14 +217,18 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "pre-wrap", Value_PreWrap },
{ "ridge", Value_Ridge },
{ "right", Value_Right },
+ { "roundcap", Value_RoundCap},
+ { "roundjoin", Value_RoundJoin},
{ "selected", Value_Selected },
{ "shadow", Value_Shadow },
{ "small" , Value_Small },
{ "small-caps", Value_SmallCaps },
{ "solid", Value_Solid },
{ "square", Value_Square },
+ { "squarecap", Value_SquareCap},
{ "sub", Value_Sub },
{ "super", Value_Super },
+ { "svgmiterjoin", Value_SvgMiterJoin},
{ "text", Value_Text },
{ "top", Value_Top },
{ "transparent", Value_Transparent },
@@ -227,10 +244,10 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
};
//Map id to strings as they appears in the 'values' array above
-static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 50, 55, 35, 26, 71, 72, 25, 43, 5, 64, 48,
- 29, 59, 60, 27, 52, 62, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 51, 24, 46, 68, 37, 3, 2, 40, 63, 16,
- 11, 58, 14, 32, 65, 33, 66, 56, 67, 34, 70, 8, 28, 38, 12, 36, 61, 7, 9, 4, 69, 54, 22, 23, 30, 31,
- 1, 15, 0, 53, 45, 44 };
+static const short indexOfId[NumKnownValues] = { 0, 44, 51, 45, 52, 53, 60, 37, 28, 78, 79, 27, 46, 6, 71, 50,
+ 31, 65, 66, 29, 55, 69, 7, 11, 42, 62, 20, 14, 18, 19, 21, 23, 54, 26, 49, 75, 39, 3, 2, 43, 70, 17, 12,
+ 63, 15, 34, 72, 35, 73, 61, 74, 36, 64, 22, 56, 41, 5, 57, 67, 77, 9, 30, 40, 13, 38, 68, 8, 10, 4, 76,
+ 59, 24, 25, 32, 33, 1, 16, 0, 58, 48, 47 };
QString Value::toString() const
{
@@ -393,6 +410,8 @@ LengthData ValueExtractor::lengthValue(const Value& v)
if (data.unit != LengthData::None)
s.chop(2);
+ else if (v.type == Value::Percentage)
+ data.unit = LengthData::Percent;
data.number = s.toDouble();
return data;
@@ -406,6 +425,15 @@ static int lengthValueFromData(const LengthData& data, const QFont& f)
return qRound(qBound(double(INT_MIN) + 0.1, scale * data.number, double(INT_MAX)));
}
+QTextLength ValueExtractor::textLength(const Declaration &decl)
+{
+ const LengthData data = lengthValue(decl.d->values.at(0));
+ if (data.unit == LengthData::Percent)
+ return QTextLength(QTextLength::PercentageLength, data.number);
+
+ return QTextLength(QTextLength::FixedLength, lengthValueFromData(data, f));
+}
+
int ValueExtractor::lengthValue(const Declaration &decl)
{
if (decl.d->parsed.isValid())
@@ -812,6 +840,10 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
QStringList spreads;
spreads << "pad"_L1 << "reflect"_L1 << "repeat"_L1;
+ int coordinateMode = -1;
+ QStringList coordinateModes;
+ coordinateModes << "logical"_L1 << "stretchtodevice"_L1 << "objectbounding"_L1 << "object"_L1;
+
bool dependsOnThePalette = false;
Parser parser(lst.at(1));
while (parser.hasNext()) {
@@ -838,11 +870,12 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
parser.next();
QCss::Value value;
(void)parser.parseTerm(&value);
- if (attr.compare("spread"_L1, Qt::CaseInsensitive) == 0) {
+ if (attr.compare("spread"_L1, Qt::CaseInsensitive) == 0)
spread = spreads.indexOf(value.variant.toString());
- } else {
+ else if (attr.compare("coordinatemode"_L1, Qt::CaseInsensitive) == 0)
+ coordinateMode = coordinateModes.indexOf(value.variant.toString());
+ else
vars[attr] = value.variant.toReal();
- }
}
parser.skipSpace();
(void)parser.test(COMMA);
@@ -851,7 +884,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
if (gradType == 0) {
QLinearGradient lg(vars.value("x1"_L1), vars.value("y1"_L1),
vars.value("x2"_L1), vars.value("y2"_L1));
- lg.setCoordinateMode(QGradient::ObjectBoundingMode);
+ lg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
lg.setStops(stops);
if (spread != -1)
lg.setSpread(QGradient::Spread(spread));
@@ -865,7 +898,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
QRadialGradient rg(vars.value("cx"_L1), vars.value("cy"_L1),
vars.value("radius"_L1), vars.value("fx"_L1),
vars.value("fy"_L1));
- rg.setCoordinateMode(QGradient::ObjectBoundingMode);
+ rg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
rg.setStops(stops);
if (spread != -1)
rg.setSpread(QGradient::Spread(spread));
@@ -877,7 +910,7 @@ static BrushData parseBrushValue(const QCss::Value &v, const QPalette &pal)
if (gradType == 2) {
QConicalGradient cg(vars.value("cx"_L1), vars.value("cy"_L1), vars.value("angle"_L1));
- cg.setCoordinateMode(QGradient::ObjectBoundingMode);
+ cg.setCoordinateMode(coordinateMode < 0 ? QGradient::ObjectBoundingMode : QGradient::CoordinateMode(coordinateMode));
cg.setStops(stops);
if (spread != -1)
cg.setSpread(QGradient::Spread(spread));
@@ -979,9 +1012,11 @@ void ValueExtractor::borderValue(const Declaration &decl, int *width, QCss::Bord
}
data.color = parseBrushValue(decl.d->values.at(i), pal);
- *color = brushFromData(data.color, pal);
- if (data.color.type != BrushData::DependsOnThePalette)
- decl.d->parsed = QVariant::fromValue<BorderData>(data);
+ if (data.color.type != BrushData::Invalid) {
+ *color = brushFromData(data.color, pal);
+ if (data.color.type != BrushData::DependsOnThePalette)
+ decl.d->parsed = QVariant::fromValue<BorderData>(data);
+ }
}
static void parseShorthandBackgroundProperty(const QList<QCss::Value> &values, BrushData *brush, QString *image, Repeat *repeat, Qt::Alignment *alignment, const QPalette &pal)
@@ -1347,7 +1382,7 @@ bool ValueExtractor::extractPalette(QBrush *foreground,
QBrush *selectedBackground,
QBrush *alternateBackground,
QBrush *placeHolderTextForeground,
- QBrush *accentColor)
+ QBrush *accent)
{
bool hit = false;
for (int i = 0; i < declarations.size(); ++i) {
@@ -1358,7 +1393,7 @@ bool ValueExtractor::extractPalette(QBrush *foreground,
case QtSelectionBackground: *selectedBackground = decl.brushValue(pal); break;
case QtAlternateBackground: *alternateBackground = decl.brushValue(pal); break;
case QtPlaceHolderTextColor: *placeHolderTextForeground = decl.brushValue(pal); break;
- case QtAccentColor: *accentColor = decl.brushValue(pal); break;
+ case QtAccent: *accent = decl.brushValue(pal); break;
default: continue;
}
hit = true;
@@ -1813,6 +1848,35 @@ bool Declaration::borderCollapseValue() const
return d->values.at(0).toString() == "collapse"_L1;
}
+QList<qreal> Declaration::dashArray() const
+{
+ if (d->propertyId != Property::QtStrokeDashArray || d->values.empty())
+ return QList<qreal>();
+
+ bool isValid = true;
+ QList<qreal> dashes;
+ for (int i = 0; i < d->values.size(); i++) {
+ Value v = d->values[i];
+ // Separators must be at odd indices and Numbers at even indices.
+ bool isValidSeparator = (i & 1) && v.type == Value::TermOperatorComma;
+ bool isValidNumber = !(i & 1) && v.type == Value::Number;
+ if (!isValidNumber && !isValidSeparator) {
+ isValid = false;
+ break;
+ } else if (isValidNumber) {
+ bool ok;
+ dashes.append(v.variant.toReal(&ok));
+ if (!ok) {
+ isValid = false;
+ break;
+ }
+ }
+ }
+
+ isValid &= !(dashes.size() & 1);
+ return isValid ? dashes : QList<qreal>();
+}
+
QIcon Declaration::iconValue() const
{
if (d->parsed.isValid())
diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h
index 20177312af..ba4a611df3 100644
--- a/src/gui/text/qcssparser_p.h
+++ b/src/gui/text/qcssparser_p.h
@@ -166,7 +166,15 @@ enum Property {
WordSpacing,
TextDecorationColor,
QtPlaceHolderTextColor,
- QtAccentColor,
+ QtAccent,
+ QtStrokeWidth,
+ QtStrokeColor,
+ QtStrokeLineCap,
+ QtStrokeLineJoin,
+ QtStrokeMiterLimit,
+ QtStrokeDashArray,
+ QtStrokeDashOffset,
+ QtForeground,
NumProperties
};
@@ -223,6 +231,13 @@ enum KnownValue {
Value_SmallCaps,
Value_Uppercase,
Value_Lowercase,
+ Value_SquareCap,
+ Value_FlatCap,
+ Value_RoundCap,
+ Value_MiterJoin,
+ Value_BevelJoin,
+ Value_RoundJoin,
+ Value_SvgMiterJoin,
/* keep these in same order as QPalette::ColorRole */
Value_FirstColorRole,
@@ -389,7 +404,7 @@ QT_CSS_DECLARE_TYPEINFO(BackgroundData, Q_RELOCATABLE_TYPE)
struct LengthData {
qreal number;
- enum { None, Px, Ex, Em } unit;
+ enum { None, Px, Ex, Em, Percent } unit;
};
QT_CSS_DECLARE_TYPEINFO(LengthData, Q_PRIMITIVE_TYPE)
@@ -448,6 +463,8 @@ struct Q_GUI_EXPORT Declaration
void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const;
bool borderCollapseValue() const;
+
+ QList<qreal> dashArray() const;
};
QT_CSS_DECLARE_TYPEINFO(Declaration, Q_RELOCATABLE_TYPE)
@@ -826,12 +843,13 @@ struct Q_GUI_EXPORT ValueExtractor
bool extractOutline(int *borders, QBrush *colors, BorderStyle *Styles, QSize *radii, int *offsets);
bool extractPalette(QBrush *foreground, QBrush *selectedForeground, QBrush *selectedBackground,
QBrush *alternateBackground, QBrush *placeHolderTextForeground,
- QBrush *accentColor);
+ QBrush *accent);
int extractStyleFeatures();
bool extractImage(QIcon *icon, Qt::Alignment *a, QSize *size);
bool extractIcon(QIcon *icon, QSize *size);
void lengthValues(const Declaration &decl, int *m);
+ QTextLength textLength(const Declaration &decl);
private:
void extractFont();
diff --git a/src/gui/text/qdistancefield.cpp b/src/gui/text/qdistancefield.cpp
index adbde11237..fe230188c6 100644
--- a/src/gui/text/qdistancefield.cpp
+++ b/src/gui/text/qdistancefield.cpp
@@ -1058,7 +1058,7 @@ QImage QDistanceField::toImage(QImage::Format format) const
}
if (image.format() != format)
- image = image.convertToFormat(format);
+ image = std::move(image).convertToFormat(format);
}
return image;
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index fb8ec90dd7..f3a35a4269 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -90,6 +90,9 @@ bool QFontDef::exactMatch(const QFontDef &other) const
return false;
}
+ if (variableAxisValues != other.variableAxisValues)
+ return false;
+
return (styleHint == other.styleHint
&& styleStrategy == other.styleStrategy
&& weight == other.weight
@@ -346,14 +349,32 @@ void QFontPrivate::resolve(uint mask, const QFontPrivate *other)
if (!(mask & QFont::FeaturesResolved))
features = other->features;
+
+ if (!(mask & QFont::VariableAxesResolved))
+ request.variableAxisValues = other->request.variableAxisValues;
+}
+
+bool QFontPrivate::hasVariableAxis(QFont::Tag tag, float value) const
+{
+ return request.variableAxisValues.contains(tag) && request.variableAxisValues.value(tag) == value;
+}
+
+void QFontPrivate::setVariableAxis(QFont::Tag tag, float value)
+{
+ request.variableAxisValues.insert(tag, value);
+}
+
+void QFontPrivate::unsetVariableAxis(QFont::Tag tag)
+{
+ request.variableAxisValues.remove(tag);
}
-void QFontPrivate::setFeature(quint32 tag, quint32 value)
+void QFontPrivate::setFeature(QFont::Tag tag, quint32 value)
{
features.insert(tag, value);
}
-void QFontPrivate::unsetFeature(quint32 tag)
+void QFontPrivate::unsetFeature(QFont::Tag tag)
{
features.remove(tag);
}
@@ -471,8 +492,6 @@ QFontEngineData::~QFontEngineData()
The font matching algorithm works as follows:
\list 1
\li The specified font families (set by setFamilies()) are searched for.
- \li If not found, then if set the specified font family exists and can be used to represent
- the writing system in use, it will be selected.
\li If not, a replacement font that supports the writing system is
selected. The font matching algorithm will try to find the
best match for all the properties set in the QFont. How this is
@@ -542,7 +561,7 @@ QFontEngineData::~QFontEngineData()
Information on encodings can be found from the
\l{UTR17} page.
- \sa QFontMetrics, QFontInfo, QFontDatabase, {Character Map Example}
+ \sa QFontMetrics, QFontInfo, QFontDatabase
*/
/*!
@@ -923,12 +942,6 @@ int QFont::pointSize() const
\li No hinting
\endtable
- \note Please be aware that altering the hinting preference on Windows is available through
- the DirectWrite font engine. This is available on Windows Vista after installing the platform
- update, and on Windows 7. In order to use this extension, configure Qt using -directwrite.
- The target application will then depend on the availability of DirectWrite on the target
- system.
-
*/
/*!
@@ -1449,6 +1462,14 @@ QFont::StyleHint QFont::styleHint() const
\value NoAntialias don't antialias the fonts.
\value NoSubpixelAntialias avoid subpixel antialiasing on the fonts if possible.
\value PreferAntialias antialias if possible.
+ \value [since 6.8] ContextFontMerging If the selected font does not contain a certain character,
+ then Qt automatically chooses a similar-looking fallback font that contains the
+ character. By default this is done on a character-by-character basis. This means that in
+ certain uncommon cases, multiple fonts may be used to represent one string of text even
+ if it's in the same script. Setting \c ContextFontMerging will try finding the fallback
+ font that matches the largest subset of the input string instead. This will be more
+ expensive for strings where missing glyphs occur, but may give more consistent results.
+ If \c NoFontMerging is set, then \c ContextFontMerging will have no effect.
\value NoFontMerging If the font selected for a certain writing system
does not contain a character requested to draw, then Qt automatically chooses a similar
looking font that contains the character. The NoFontMerging flag disables this feature.
@@ -1521,7 +1542,7 @@ void QFont::setStyleStrategy(StyleStrategy s)
Predefined stretch values that follow the CSS naming convention. The higher
the value, the more stretched the text is.
- \value AnyStretch 0 Accept any stretch matched using the other QFont properties (added in Qt 5.8)
+ \value [since 5.8] AnyStretch 0 Accept any stretch matched using the other QFont properties
\value UltraCondensed 50
\value ExtraCondensed 62
\value Condensed 75
@@ -1802,13 +1823,29 @@ bool QFont::operator<(const QFont &f) const
if (d->features.size() != f.d->features.size())
return f.d->features.size() < d->features.size();
- auto it = d->features.constBegin();
- auto jt = f.d->features.constBegin();
- for (; it != d->features.constEnd(); ++it, ++jt) {
- if (it.key() != jt.key())
- return jt.key() < it.key();
- if (it.value() != jt.value())
- return jt.value() < it.value();
+ {
+ auto it = d->features.constBegin();
+ auto jt = f.d->features.constBegin();
+ for (; it != d->features.constEnd(); ++it, ++jt) {
+ if (it.key() != jt.key())
+ return jt.key() < it.key();
+ if (it.value() != jt.value())
+ return jt.value() < it.value();
+ }
+ }
+
+ if (r1.variableAxisValues.size() != r2.variableAxisValues.size())
+ return r1.variableAxisValues.size() < r2.variableAxisValues.size();
+
+ {
+ auto it = r1.variableAxisValues.constBegin();
+ auto jt = r2.variableAxisValues.constBegin();
+ for (; it != r1.variableAxisValues.constEnd(); ++it, ++jt) {
+ if (it.key() != jt.key())
+ return jt.key() < it.key();
+ if (it.value() != jt.value())
+ return jt.value() < it.value();
+ }
}
return false;
@@ -2233,177 +2270,400 @@ void QFont::cacheStatistics()
}
/*!
- \since 6.6
+ \class QFont::Tag
+ \brief The QFont::Tag type provides access to advanced font features.
+ \since 6.7
+ \inmodule QtGui
- Applies integer values to specific OpenType features when shaping the text based on the contents
- in \a features. This provides advanced access to the font shaping process, and can be used
- to support font features that are otherwise not covered in the API.
+ QFont provides access to advanced features when shaping text. A feature is defined
+ by a tag, which can be represented as a four-character string, or as a 32bit integer
+ value. This type represents such a tag in a type-safe way. It can be constructed from
+ a four-character, 8bit string literal, or from a corresponding 32bit integer value.
+ Using a shorter or longer string literal will result in a compile-time error.
- An OpenType feature is defined by a 32-bit tag (encoded from the four-character name of the
- table by using the stringToTag() function), as well as an integer value.
+ \code
+ QFont font;
+ // Correct
+ font.setFeature("frac");
- This integer value passed along with the tag in most cases represents a boolean value: A zero
- value means the feature is disabled, and a non-zero value means it is enabled. For certain
- font features, however, it may have other intepretations. For example, when applied to the
- \c salt feature, the value is an index that specifies the stylistic alternative to use.
+ // Wrong - won't compile
+ font.setFeature("fraction");
- For example, the \c frac font feature will convert diagonal fractions separated with a slash
- (such as \c 1/2) with a different representation. Typically this will involve baking the full
- fraction into a single character width (such as \c ½).
+ // Wrong - will produce runtime warning and fail
+ font.setFeature(u"fraction"_s);
+ \endcode
- If a font supports the \c frac feature, then it can be enabled in the shaper by setting
- \c{features[stringToTag("frac")] = 1} in the font feature map.
+ The named constructors allow to create a tag from an 32bit integer or string value,
+ and will return a \c std::nullopt when the input is invalid.
- This function will overwrite the current list of explicit font features. Use setFeature() or
- unsetFeature() to set or unset individual features.
+ \sa QFont::setFeature(), QFont::featureTags()
+*/
- \note By default, Qt will enable and disable certain font features based on other font
- properties. In particular, the \c kern feature will be enabled/disabled depending on the
- \l kerning() property of the QFont. In addition, all ligature features
- (\c liga, \c clig, \c dlig, \c hlig) will be disabled if a \l letterSpacing() is applied,
- but only for writing systems where the use of ligature is cosmetic. For writing systems where
- ligatures are required, the features will remain in their default state. The values set using
- setFeatures() and related functions will override the default behavior. If, for instance,
- the \c{features[stringToTag("kern")]} is set to 1, then kerning will always be enabled,
- regardless of whether the kerning property is set to false. Similarly, if it is set to 0, then
- it will always be disabled. To reset a font feature to its default behavior, you can unset it
- in the features hash, for example by using unsetFeature().
+/*!
+ \fn QFont::Tag::Tag()
+
+ Default constructor, producing an invalid tag.
+*/
+
+/*!
+ \fn template <size_t N> QFont::Tag::Tag(const char (&str)[N]) noexcept
+
+ Constructs a tag from a string literal, \a str. The literal must be exactly four
+ characters long.
+
+ \code
+ font.setFeature("frac", 1);
+ \endcode
+
+ \sa fromString(), fromValue()
+*/
+
+/*!
+ \fn bool QFont::Tag::comparesEqual(const QFont::Tag &lhs, const QFont::Tag &rhs) noexcept
+ \fn Qt::strong_ordering QFont::Tag::compareThreeWay(const QFont::Tag &lhs, const QFont::Tag &rhs) noexcept
+
+ Compare \a lhs with \a rhs for equality and ordering.
+*/
+
+/*!
+ \fn size_t QFont::Tag::qHash(QFont::Tag key, size_t seed) noexcept
+
+ Returns the hash value for \a key, using \a seed to seed the calculation.
+*/
+
+/*!
+ \fn quint32 QFont::Tag::value() const noexcept
+
+ Returns the numerical value of this tag.
+
+ \sa isValid(), fromValue()
+*/
+
+/*!
+ \fn bool QFont::Tag::isValid() const noexcept
+
+ Returns whether the tag is valid. A tag is valid if its value is not zero.
+
+ \sa value(), fromValue(), fromString()
+*/
+
+/*!
+ \fn QByteArray QFont::Tag::toString() const noexcept
+
+ Returns the string representation of this tag as a byte array.
+
+ \sa fromString()
+*/
+
+/*!
+ \fn std::optional<QFont::Tag> QFont::Tag::fromValue(quint32 value) noexcept
+
+ Returns a tag constructed from \a value, or \c std::nullopt if the tag produced
+ would be invalid.
+
+ \sa isValid()
+*/
- \sa setFeature(), unsetFeature(), features()
+/*!
+ Returns a tag constructed from the string in \a view. The string must be exactly
+ four characters long.
+
+ Returns \c std::nullopt if the input is not four characters long, or if the tag
+ produced would be invalid.
+
+ \sa isValid(), fromValue()
*/
-void QFont::setFeatures(const QHash<quint32, quint32> &features)
+std::optional<QFont::Tag> QFont::Tag::fromString(QAnyStringView view) noexcept
{
- d->detachButKeepEngineData(this);
- d->features = features;
- resolve_mask |= QFont::FeaturesResolved;
+ if (view.size() != 4) {
+ qWarning("The tag name must be exactly 4 characters long!");
+ return std::nullopt;
+ }
+ const QFont::Tag maybeTag = view.visit([](auto view) {
+ using CharType = decltype(view.at(0));
+ if constexpr (std::is_same_v<CharType, char>) {
+ const char bytes[5] = { view.at(0), view.at(1), view.at(2), view.at(3), 0 };
+ return Tag(bytes);
+ } else {
+ const char bytes[5] = { view.at(0).toLatin1(), view.at(1).toLatin1(),
+ view.at(2).toLatin1(), view.at(3).toLatin1(), 0 };
+ return Tag(bytes);
+ }
+ });
+ return maybeTag.isValid() ? std::optional<Tag>(maybeTag) : std::nullopt;
}
/*!
- \since 6.6
- \overload
+ \fn QDataStream &operator<<(QDataStream &, QFont::Tag)
+ \fn QDataStream &operator>>(QDataStream &, QFont::Tag &)
+ \relates QFont::Tag
+
+ Data stream operators for QFont::Tag.
+*/
+
+/*!
+ \since 6.7
- Sets the \a value for a specific font feature \a tag. This is an advanced feature which can be
- used to enable or disable specific OpenType features if they are available in the font. See
- \l setFeatures() for more details.
+ Applies a \a value to the variable axis corresponding to \a tag.
+
+ Variable fonts provide a way to store multiple variations (with different weights, widths
+ or styles) in the same font file. The variations are given as floating point values for
+ a pre-defined set of parameters, called "variable axes". Specific instances are typically
+ given names by the font designer, and, in Qt, these can be selected using setStyleName()
+ just like traditional sub-families.
+
+ In some cases, it is also useful to provide arbitrary values for the different axes. For
+ instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these.
+ You could then manually request this by supplying a custom value for the "wght" axis in the
+ font.
+
+ \code
+ QFont font;
+ font.setVariableAxis("wght", (QFont::Normal + QFont::Bold) / 2.0f);
+ \endcode
+
+ If the "wght" axis is supported by the font and the given value is within its defined range,
+ a font corresponding to the weight 550.0 will be provided.
+
+ There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width),
+ "ital" (italic) and "opsz" (optical size). They each have indivdual ranges defined in the font
+ itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas
+ "ital" can span from 0 to 1 (from not italic to fully italic).
+
+ A font may also choose to define custom axes; the only limitation is that the name has to
+ meet the requirements for a QFont::Tag (sequence of four latin-1 characters.)
+
+ By default, no variable axes are set.
+
+ \note On Windows, variable axes are not supported if the optional GDI font backend is in use.
+
+ \sa unsetVariableAxis
+ */
+void QFont::setVariableAxis(Tag tag, float value)
+{
+ if (tag.isValid()) {
+ if (resolve_mask & QFont::VariableAxesResolved && d->hasVariableAxis(tag, value))
+ return;
- \sa setFeatures(), unsetFeature(), features()
+ detach();
+
+ d->setVariableAxis(tag, value);
+ resolve_mask |= QFont::VariableAxesResolved;
+ }
+}
+
+/*!
+ \since 6.7
+
+ Unsets a previously set variable axis value given by \a tag.
+
+ \note If no value has previously been given for this tag, the QFont will still consider its
+ variable axes as set when resolving against other QFont values.
+
+ \sa setVariableAxis
*/
-void QFont::setFeature(quint32 tag, quint32 value)
+void QFont::unsetVariableAxis(Tag tag)
{
- d->detachButKeepEngineData(this);
- d->setFeature(tag, value);
- resolve_mask |= QFont::FeaturesResolved;
+ if (tag.isValid()) {
+ detach();
+
+ d->unsetVariableAxis(tag);
+ resolve_mask |= QFont::VariableAxesResolved;
+ }
}
/*!
- \since 6.6
- \overload
+ \since 6.7
+
+ Returns a list of tags for all variable axes currently set on this QFont.
+
+ See \l{QFont::}{setVariableAxis()} for more details on variable axes.
+
+ \sa QFont::Tag, setVariableAxis(), unsetVariableAxis(), isVariableAxisSet(), clearVariableAxes()
+*/
+QList<QFont::Tag> QFont::variableAxisTags() const
+{
+ return d->request.variableAxisValues.keys();
+}
+
+/*!
+ \since 6.7
+
+ Returns the value set for a specific variable axis \a tag. If the tag has not been set, 0.0 will
+ be returned instead.
+
+ See \l{QFont::}{setVariableAxis()} for more details on variable axes.
+
+ \sa QFont::Tag, setVariableAxis(), unsetVariableAxis(), isVariableAxisSet(), clearVariableAxes()
+*/
+float QFont::variableAxisValue(Tag tag) const
+{
+ return d->request.variableAxisValues.value(tag);
+}
+
+/*!
+ \since 6.7
+
+ Returns true if a value for the variable axis given by \a tag has been set on the QFont,
+ otherwise returns false.
+
+ See \l{QFont::}{setVariableAxis()} for more details on font variable axes.
+
+ \sa QFont::Tag, setVariableAxis(), unsetVariableAxis(), variableAxisValue(), clearVariableAxes()
+*/
+bool QFont::isVariableAxisSet(Tag tag) const
+{
+ return d->request.variableAxisValues.contains(tag);
+}
+
+/*!
+ \since 6.7
- Sets the \a value of a specific \a feature. This is an advanced feature which can be used to
- enable or disable specific OpenType features if they are available in the font. See
- \l setFeatures() for more details.
+ Clears any previously set variable axis values on the QFont.
- \note This is equivalent to calling setFeature(stringToTag(feature), value).
+ See \l{QFont::}{setVariableAxis()} for more details on variable axes.
- \sa setFeatures(), unsetFeature(), features()
+ \sa QFont::Tag, setVariableAxis(), unsetVariableAxis(), isVariableAxisSet(), variableAxisValue()
*/
-void QFont::setFeature(const char *feature, quint32 value)
+void QFont::clearVariableAxes()
{
- setFeature(stringToTag(feature), value);
+ if (d->request.variableAxisValues.isEmpty())
+ return;
+
+ detach();
+ d->request.variableAxisValues.clear();
}
+
/*!
- \since 6.6
+ \since 6.7
\overload
- Unsets the \a tag from the map of explicitly enabled/disabled features.
+ Applies an integer value to the typographical feature specified by \a tag when shaping the
+ text. This provides advanced access to the font shaping process, and can be used to support
+ font features that are otherwise not covered in the API.
- \note Even if the feature has not previously been added, this will mark the font features map
- as modified in this QFont, so that it will take precedence when resolving against other fonts.
+ The feature is specified by a \l{QFont::Tag}{tag}, which is typically encoded from the
+ four-character feature name in the font feature map.
- Unsetting an existing feature on the QFont reverts behavior to the default. See
- \l setFeatures() for more details.
+ This integer \a value passed along with the tag in most cases represents a boolean value: A zero
+ value means the feature is disabled, and a non-zero value means it is enabled. For certain
+ font features, however, it may have other interpretations. For example, when applied to the
+ \c salt feature, the value is an index that specifies the stylistic alternative to use.
- \sa setFeatures(), setFeature(), features()
+ For example, the \c frac font feature will convert diagonal fractions separated with a slash
+ (such as \c 1/2) with a different representation. Typically this will involve baking the full
+ fraction into a single character width (such as \c ½).
+
+ If a font supports the \c frac feature, then it can be enabled in the shaper by setting
+ \c{features["frac"] = 1} in the font feature map.
+
+ \note By default, Qt will enable and disable certain font features based on other font
+ properties. In particular, the \c kern feature will be enabled/disabled depending on the
+ \l kerning() property of the QFont. In addition, all ligature features
+ (\c liga, \c clig, \c dlig, \c hlig) will be disabled if a \l letterSpacing() is applied,
+ but only for writing systems where the use of ligature is cosmetic. For writing systems where
+ ligatures are required, the features will remain in their default state. The values set using
+ setFeature() and related functions will override the default behavior. If, for instance,
+ the feature "kern" is set to 1, then kerning will always be enabled, regardless of whether the
+ kerning property is set to false. Similarly, if it is set to 0, then it will always be disabled.
+ To reset a font feature to its default behavior, you can unset it using unsetFeature().
+
+ \sa QFont::Tag, clearFeatures(), setFeature(), unsetFeature(), featureTags()
*/
-void QFont::unsetFeature(quint32 tag)
+void QFont::setFeature(Tag tag, quint32 value)
{
- d->detachButKeepEngineData(this);
- d->unsetFeature(tag);
- resolve_mask |= QFont::FeaturesResolved;
+ if (tag.isValid()) {
+ d->detachButKeepEngineData(this);
+ d->setFeature(tag, value);
+ resolve_mask |= QFont::FeaturesResolved;
+ }
}
/*!
- \since 6.6
+ \since 6.7
\overload
- Unsets the \a feature from the map of explicitly enabled/disabled features.
+ Unsets the \a tag from the map of explicitly enabled/disabled features.
\note Even if the feature has not previously been added, this will mark the font features map
as modified in this QFont, so that it will take precedence when resolving against other fonts.
- Unsetting an existing feature on the QFont reverts behavior to the default. See
- \l setFeatures() for more details.
+ Unsetting an existing feature on the QFont reverts behavior to the default.
- \note This is equivalent to calling unsetFeature(stringToTag(feature)).
+ See \l setFeature() for more details on font features.
- \sa setFeatures(), setFeature(), features()
+ \sa QFont::Tag, clearFeatures(), setFeature(), featureTags(), featureValue()
*/
-void QFont::unsetFeature(const char *feature)
+void QFont::unsetFeature(Tag tag)
{
- unsetFeature(stringToTag(feature));
+ if (tag.isValid()) {
+ d->detachButKeepEngineData(this);
+ d->unsetFeature(tag);
+ resolve_mask |= QFont::FeaturesResolved;
+ }
}
/*!
- \since 6.6
+ \since 6.7
+
+ Returns a list of tags for all font features currently set on this QFont.
+
+ See \l{QFont::}{setFeature()} for more details on font features.
+
+ \sa QFont::Tag, setFeature(), unsetFeature(), isFeatureSet(), clearFeatures()
+*/
+QList<QFont::Tag> QFont::featureTags() const
+{
+ return d->features.keys();
+}
- Returns the hash of explicitly set font features in the QFont. By default this map is empty and
- the shaping process will use default features based on other font or text properties.
+/*!
+ \since 6.7
- Unsetting an existing feature on the QFont reverts behavior to the default. See
- \l setFeatures() for more details.
+ Returns the value set for a specific feature \a tag. If the tag has not been set, 0 will be
+ returned instead.
- The key of the returned QHash refers to the font table tag as it's encoded in the font
- file. It can be converted to a QByteArray using the tagToString() function.
+ See \l{QFont::}{setFeature()} for more details on font features.
- \sa setFeatures(), setFeature(), unsetFeature()
+ \sa QFont::Tag, setFeature(), unsetFeature(), featureTags(), isFeatureSet()
*/
-QHash<quint32, quint32> QFont::features() const
+quint32 QFont::featureValue(Tag tag) const
{
- return d->features;
+ return d->features.value(tag);
}
/*!
- \since 6.6
+ \since 6.7
- Returns the decoded name for \a tag.
+ Returns true if a value for the feature given by \a tag has been set on the QFont, otherwise
+ returns false.
- \sa setFeatures(), setFeature(), unsetFeature(), stringToTag()
+ See \l{QFont::}{setFeature()} for more details on font features.
+
+ \sa QFont::Tag, setFeature(), unsetFeature(), featureTags(), featureValue()
*/
-QByteArray QFont::tagToString(quint32 tag)
+bool QFont::isFeatureSet(Tag tag) const
{
- char str[4] =
- { char((tag & 0xff000000) >> 24),
- char((tag & 0x00ff0000) >> 16),
- char((tag & 0x0000ff00) >> 8),
- char((tag & 0x000000ff)) };
- return QByteArray(str, 4);
+ return d->features.contains(tag);
}
/*!
- \since 6.6
+ \since 6.7
+
+ Clears any previously set features on the QFont.
- Returns the encoded tag for \a name. The \a name must be a null-terminated string of exactly
- four characters. Returns 0 on error.
+ See \l{QFont::}{setFeature()} for more details on font features.
- \sa setFeatures(), setFeature(), unsetFeature(), tagToString()
+ \sa QFont::Tag, setFeature(), unsetFeature(), featureTags(), featureValue()
*/
-quint32 QFont::stringToTag(const char *name)
+void QFont::clearFeatures()
{
- if (qstrlen(name) != 4)
- return 0;
+ if (d->features.isEmpty())
+ return;
- return MAKE_TAG(name[0], name[1], name[2], name[3]);
+ d->detachButKeepEngineData(this);
+ d->features.clear();
}
extern QStringList qt_fallbacksForFamily(const QString &family, QFont::Style style,
@@ -2484,9 +2744,9 @@ void QFont::setFamilies(const QStringList &families)
QDataStream &operator<<(QDataStream &s, const QFont &font)
{
if (s.version() == 1) {
- s << font.d->request.families.first().toLatin1();
+ s << font.d->request.families.constFirst().toLatin1();
} else {
- s << font.d->request.families.first();
+ s << font.d->request.families.constFirst();
if (s.version() >= QDataStream::Qt_5_4)
s << font.d->request.styleName;
}
@@ -2544,6 +2804,8 @@ QDataStream &operator<<(QDataStream &s, const QFont &font)
}
if (s.version() >= QDataStream::Qt_6_6)
s << font.d->features;
+ if (s.version() >= QDataStream::Qt_6_7)
+ s << font.d->request.variableAxisValues;
return s;
}
@@ -2662,10 +2924,31 @@ QDataStream &operator>>(QDataStream &s, QFont &font)
font.d->features.clear();
s >> font.d->features;
}
+ if (s.version() >= QDataStream::Qt_6_7) {
+ font.d->request.variableAxisValues.clear();
+ s >> font.d->request.variableAxisValues;
+ }
return s;
}
+QDataStream &operator<<(QDataStream &stream, QFont::Tag tag)
+{
+ stream << tag.value();
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, QFont::Tag &tag)
+{
+ quint32 value;
+ stream >> value;
+ if (const auto maybeTag = QFont::Tag::fromValue(value))
+ tag = *maybeTag;
+ else
+ stream.setStatus(QDataStream::ReadCorruptData);
+ return stream;
+}
+
#endif // QT_NO_DATASTREAM
@@ -2716,6 +2999,35 @@ QDataStream &operator>>(QDataStream &s, QFont &font)
info object is \e not updated.
\endlist
+ \section1 Checking for the existence of a font
+
+ Sometimes it can be useful to check if a font exists before attempting
+ to use it. The most thorough way of doing so is by using \l {exactMatch()}:
+
+ \code
+ const QFont segoeFont(QLatin1String("Segoe UI"));
+ if (QFontInfo(segoeFont).exactMatch()) {
+ // Use the font...
+ }
+ \endcode
+
+ However, this deep search of families can be expensive on some platforms.
+ \c QFontDatabase::families().contains() is a faster, but less thorough
+ alternative:
+
+ \code
+ const QLatin1String segoeUiFamilyName("Segoe UI");
+ if (QFontDatabase::families().contains(segoeUiFamilyName)) {
+ const QFont segoeFont(segoeUiFamilyName);
+ // Use the font...
+ }
+ \endcode
+
+ It's less thorough because it's not a complete search: some font family
+ aliases may be missing from the list. However, this approach results in
+ faster application startup times, and so should always be preferred if
+ possible.
+
\sa QFont, QFontMetrics, QFontDatabase
*/
@@ -2732,6 +3044,8 @@ QDataStream &operator>>(QDataStream &s, QFont &font)
Use QPainter::fontInfo() to get the font info when painting.
This will give correct results also when painting on paint device
that is not screen-compatible.
+
+ \sa {Checking for the existence of a font}
*/
QFontInfo::QFontInfo(const QFont &font)
: d(font.d)
@@ -2773,13 +3087,13 @@ QFontInfo &QFontInfo::operator=(const QFontInfo &fi)
/*!
Returns the family name of the matched window system font.
- \sa QFont::family()
+ \sa QFont::family(), {Checking for the existence of a font}
*/
QString QFontInfo::family() const
{
QFontEngine *engine = d->engineForScript(QChar::Script_Common);
Q_ASSERT(engine != nullptr);
- return engine->fontDef.families.isEmpty() ? QString() : engine->fontDef.families.first();
+ return engine->fontDef.families.isEmpty() ? QString() : engine->fontDef.families.constFirst();
}
/*!
@@ -2958,7 +3272,7 @@ bool QFontInfo::fixedPitch() const
QChar ch[2] = { u'i', u'm' };
QGlyphLayoutArray<2> g;
int l = 2;
- if (!engine->stringToCMap(ch, 2, &g, &l, {}))
+ if (engine->stringToCMap(ch, 2, &g, &l, {}) < 0)
Q_UNREACHABLE();
Q_ASSERT(l == 2);
engine->fontDef.fixedPitch = g.advances[0] == g.advances[1];
@@ -3511,6 +3825,13 @@ QDebug operator<<(QDebug stream, const QFont &font)
return stream;
}
+
+QDebug operator<<(QDebug debug, QFont::Tag tag)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote() << tag.toString();
+ return debug;
+}
#endif
QT_END_NAMESPACE
diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h
index b54df8d2df..66a5f7c155 100644
--- a/src/gui/text/qfont.h
+++ b/src/gui/text/qfont.h
@@ -4,6 +4,8 @@
#ifndef QFONT_H
#define QFONT_H
+#include <QtCore/qcompare.h>
+#include <QtCore/qendian.h>
#include <QtCore/qshareddata.h>
#include <QtGui/qtguiglobal.h>
#include <QtGui/qwindowdefs.h>
@@ -45,6 +47,7 @@ public:
NoAntialias = 0x0100,
NoSubpixelAntialias = 0x0800,
PreferNoShaping = 0x1000,
+ ContextFontMerging = 0x2000,
NoFontMerging = 0x8000
};
Q_ENUM(StyleStrategy)
@@ -127,7 +130,8 @@ public:
StyleNameResolved = 0x10000,
FamiliesResolved = 0x20000,
FeaturesResolved = 0x40000,
- AllPropertiesResolved = 0x7ffff
+ VariableAxesResolved = 0x80000,
+ AllPropertiesResolved = 0xfffff
};
Q_ENUM(ResolveProperties)
@@ -207,15 +211,74 @@ public:
void setHintingPreference(HintingPreference hintingPreference);
HintingPreference hintingPreference() const;
- void setFeature(const char *feature, quint32 value);
- void setFeature(quint32 tag, quint32 value);
- void setFeatures(const QHash<quint32, quint32> &features);
- void unsetFeature(quint32 tag);
- void unsetFeature(const char *feature);
- QHash<quint32, quint32> features() const;
+ struct Tag
+ {
+ constexpr Tag() = default;
+
+ template <size_t N>
+ constexpr Q_IMPLICIT Tag(const char (&str)[N]) noexcept
+ : m_value((quint32(str[0]) << 24) | (quint32(str[1]) << 16)
+ | (quint32(str[2]) << 8) | quint32(str[3]))
+ {
+ static_assert(N == 5, "The tag name must be exactly 4 characters long!");
+ }
+
+ constexpr bool isValid() const noexcept { return m_value != 0; }
+ constexpr quint32 value() const noexcept { return m_value; }
+
+ QByteArray toString() const
+ {
+ const char data[] = {
+ char((m_value & 0xff000000) >> 24),
+ char((m_value & 0x00ff0000) >> 16),
+ char((m_value & 0x0000ff00) >> 8),
+ char((m_value & 0x000000ff)) };
+ return QByteArray(data, sizeof(data));
+ }
+
+ static constexpr std::optional<Tag> fromValue(quint32 value) noexcept
+ {
+ Tag maybeTag;
+ maybeTag.m_value = value;
+ return maybeTag.isValid() ? std::optional<Tag>(maybeTag) : std::nullopt;
+ }
+ Q_GUI_EXPORT static std::optional<Tag> fromString(QAnyStringView view) noexcept;
- static QByteArray tagToString(quint32 tag);
- static quint32 stringToTag(const char *tagString);
+#ifndef QT_NO_DATASTREAM
+ friend Q_GUI_EXPORT QDataStream &operator<<(QDataStream &, Tag);
+ friend Q_GUI_EXPORT QDataStream &operator>>(QDataStream &, Tag &);
+#endif
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_GUI_EXPORT QDebug operator<<(QDebug debug, Tag tag);
+#endif
+
+ friend constexpr size_t qHash(Tag key, size_t seed = 0) noexcept
+ { return qHash(key.value(), seed); }
+
+ private:
+ friend constexpr bool comparesEqual(const Tag &lhs, const Tag &rhs) noexcept
+ { return lhs.m_value == rhs.m_value; }
+ friend constexpr Qt::strong_ordering compareThreeWay(const Tag &lhs, const Tag &rhs) noexcept
+ { return Qt::compareThreeWay(lhs.m_value, rhs.m_value); }
+ Q_DECLARE_STRONGLY_ORDERED_LITERAL_TYPE(QFont::Tag)
+
+ quint32 m_value = 0;
+ };
+
+ void setFeature(Tag tag, quint32 value);
+ void unsetFeature(Tag tag);
+ quint32 featureValue(Tag tag) const;
+ bool isFeatureSet(Tag tag) const;
+ QList<Tag> featureTags() const;
+ void clearFeatures();
+
+ void setVariableAxis(Tag tag, float value);
+ void unsetVariableAxis(Tag tag);
+ bool isVariableAxisSet(Tag tag) const;
+ float variableAxisValue(Tag tag) const;
+ void clearVariableAxes();
+ QList<Tag> variableAxisTags() const;
// dupicated from QFontInfo
bool exactMatch() const;
diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h
index 3596322fae..b674e71103 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -55,6 +55,7 @@ struct QFontDef
QString styleName;
QStringList fallBackFamilies;
+ QMap<QFont::Tag, float> variableAxisValues;
qreal pointSize;
qreal pixelSize;
@@ -85,6 +86,7 @@ struct QFontDef
&& families == other.families
&& styleName == other.styleName
&& hintingPreference == other.hintingPreference
+ && variableAxisValues == other.variableAxisValues
;
}
inline bool operator<(const QFontDef &other) const
@@ -103,6 +105,22 @@ struct QFontDef
if (ignorePitch != other.ignorePitch) return ignorePitch < other.ignorePitch;
if (fixedPitch != other.fixedPitch) return fixedPitch < other.fixedPitch;
+ if (variableAxisValues != other.variableAxisValues) {
+ if (variableAxisValues.size() != other.variableAxisValues.size())
+ return variableAxisValues.size() < other.variableAxisValues.size();
+
+ {
+ auto it = variableAxisValues.constBegin();
+ auto jt = other.variableAxisValues.constBegin();
+ for (; it != variableAxisValues.constEnd(); ++it, ++jt) {
+ if (it.key() != jt.key())
+ return jt.key() < it.key();
+ if (it.value() != jt.value())
+ return jt.value() < it.value();
+ }
+ }
+ }
+
return false;
}
};
@@ -120,7 +138,9 @@ inline size_t qHash(const QFontDef &fd, size_t seed = 0) noexcept
fd.fixedPitch,
fd.families,
fd.styleName,
- fd.hintingPreference);
+ fd.hintingPreference,
+ fd.variableAxisValues.keys(),
+ fd.variableAxisValues.values());
}
class QFontEngineData
@@ -164,7 +184,7 @@ public:
QFixed letterSpacing;
QFixed wordSpacing;
- QHash<quint32, quint32> features;
+ QHash<QFont::Tag, quint32> features;
mutable QFontPrivate *scFont;
QFont smallCapsFont() const { return QFont(smallCapsFontPrivate()); }
@@ -179,8 +199,12 @@ public:
static void detachButKeepEngineData(QFont *font);
- void setFeature(quint32 tag, quint32 value);
- void unsetFeature(quint32 tag);
+ void setFeature(QFont::Tag tag, quint32 value);
+ void unsetFeature(QFont::Tag tag);
+
+ void setVariableAxis(QFont::Tag tag, float value);
+ void unsetVariableAxis(QFont::Tag tag);
+ bool hasVariableAxis(QFont::Tag tag, float value) const;
private:
QFontPrivate &operator=(const QFontPrivate &) { return *this; }
diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp
index 5dd3437e35..3d6d3b7886 100644
--- a/src/gui/text/qfontdatabase.cpp
+++ b/src/gui/text/qfontdatabase.cpp
@@ -670,7 +670,8 @@ static QStringList fallbacksForFamily(const QString &family, QFont::Style style,
return *fallbacks;
// make sure that the db has all fallback families
- QStringList retList = QGuiApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script);
+ QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Latin ? QChar::Script_Common : script);
+ QStringList retList = userFallbacks + QGuiApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script);
QStringList::iterator i;
for (i = retList.begin(); i != retList.end(); ++i) {
@@ -733,7 +734,7 @@ QFontEngine *QFontDatabasePrivate::loadSingleEngine(int script,
// Also check for OpenType tables when using complex scripts
if (Q_UNLIKELY(!engine->supportsScript(QChar::Script(script)))) {
qWarning(" OpenType support missing for \"%s\", script %d",
- qPrintable(def.families.first()), script);
+ qPrintable(def.families.constFirst()), script);
return nullptr;
}
@@ -758,7 +759,7 @@ QFontEngine *QFontDatabasePrivate::loadSingleEngine(int script,
// Also check for OpenType tables when using complex scripts
if (!engine->supportsScript(QChar::Script(script))) {
qWarning(" OpenType support missing for \"%s\", script %d",
- +qPrintable(def.families.first()), script);
+ +qPrintable(def.families.constFirst()), script);
if (engine->ref.loadRelaxed() == 0)
delete engine;
return nullptr;
@@ -1201,7 +1202,7 @@ QString QFontDatabase::styleString(const QFontInfo &fontInfo)
each combination of family and style, displaying this information
in a tree view.
- \sa QFont, QFontInfo, QFontMetrics, {Character Map Example}
+ \sa QFont, QFontInfo, QFontMetrics
*/
/*!
@@ -2186,6 +2187,8 @@ int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &
// loaded, so it has to be flushed.
QFontCache::instance()->clear();
+ fallbacksCache.clear();
+
emit qApp->fontDatabaseChanged();
return i;
@@ -2360,6 +2363,149 @@ bool QFontDatabase::removeAllApplicationFonts()
}
/*!
+ \since 6.8
+
+ Adds \a familyName as an application-defined fallback font for \a script.
+
+ When Qt encounters characters that are not supported by the selected font, it will search
+ through a list of fallback fonts to find a match for them. This ensures that combining multiple
+ scripts in a single string is possible, even if the main font does not support them.
+
+ The list of fallback fonts is selected based on the script of the string as well as other
+ conditions, such as system language.
+
+ While the system fallback list is usually sufficient, there are cases where it is useful
+ to override the default behavior. One such case is for using application fonts as fallback to
+ ensure cross-platform consistency.
+
+ In another case the application may be written in a script with regional differences and want
+ to run it untranslated in multiple regions. In this case, it might be useful to override the
+ local region's fallback with one that matches the language of the application.
+
+ By passing \a familyName to addApplicationFallbackFontFamily(), this will become the preferred
+ family when matching missing characters from \a script. The \a script must be a valid script
+ (\c QChar::Script_Latin or higher). When adding multiple fonts for the same script, they will
+ be prioritized in reverse order, so that the last family added will be checked first and so
+ on.
+
+ \note Qt's font matching algorithm considers \c{QChar::Script_Common} (undetermined script)
+ and \c{QChar::Script_Latin} the same. Adding a fallback for either of these will also apply
+ to the other.
+
+ \sa setApplicationFallbackFontFamilies(), removeApplicationFallbackFontFamily(), applicationFallbackFontFamilies()
+*/
+void QFontDatabase::addApplicationFallbackFontFamily(QChar::Script script, const QString &familyName)
+{
+ QMutexLocker locker(fontDatabaseMutex());
+
+ if (script < QChar::Script_Common) {
+ qCWarning(lcFontDb) << "Invalid script passed to addApplicationFallbackFontFamily:" << script;
+ return;
+ }
+
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
+ auto *db = QFontDatabasePrivate::instance();
+ auto it = db->applicationFallbackFontFamilies.find(script);
+ if (it == db->applicationFallbackFontFamilies.end())
+ it = db->applicationFallbackFontFamilies.insert(script, QStringList{});
+
+ it->prepend(familyName);
+
+ QFontCache::instance()->clear();
+ db->fallbacksCache.clear();
+}
+
+/*!
+ \since 6.8
+
+ Removes \a familyName from the list of application-defined fallback fonts for \a script,
+ provided that it has previously been added with \l{addApplicationFallbackFontFamily()}.
+
+ Returns true if the family name was in the list and false if it was not.
+
+ \sa addApplicationFallbackFontFamily(), setApplicationFallbackFontFamilies(), applicationFallbackFontFamilies()
+*/
+bool QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script script, const QString &familyName)
+{
+ QMutexLocker locker(fontDatabaseMutex());
+
+ if (script < QChar::Script_Common) {
+ qCWarning(lcFontDb) << "Invalid script passed to removeApplicationFallbackFontFamily:" << script;
+ return false;
+ }
+
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
+ auto *db = QFontDatabasePrivate::instance();
+ auto it = db->applicationFallbackFontFamilies.find(script);
+ if (it != db->applicationFallbackFontFamilies.end()) {
+ if (it->removeAll(familyName) > 0) {
+ if (it->isEmpty())
+ it = db->applicationFallbackFontFamilies.erase(it);
+ QFontCache::instance()->clear();
+ db->fallbacksCache.clear();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*!
+ \since 6.8
+
+ Sets the list of application-defined fallback fonts for \a script to \a familyNames.
+
+ When Qt encounters a character in \a script which is not supported by the current font, it will
+ check the families in \a familyNames, in order from first to last, until it finds a match. See
+ \l{addApplicationFallbackFontFamily()} for more details.
+
+ This function overwrites the current list of application-defined fallback fonts for \a script.
+
+ \sa addApplicationFallbackFontFamily(), removeApplicationFallbackFontFamily(), applicationFallbackFontFamilies()
+*/
+void QFontDatabase::setApplicationFallbackFontFamilies(QChar::Script script, const QStringList &familyNames)
+{
+ QMutexLocker locker(fontDatabaseMutex());
+
+ if (script < QChar::Script_Common) {
+ qCWarning(lcFontDb) << "Invalid script passed to setApplicationFallbackFontFamilies:" << script;
+ return;
+ }
+
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
+ auto *db = QFontDatabasePrivate::instance();
+ db->applicationFallbackFontFamilies[script] = familyNames;
+
+ QFontCache::instance()->clear();
+ db->fallbacksCache.clear();
+}
+
+/*!
+ \since 6.8
+
+ Returns the list of application-defined fallback font families previously added for \a script
+ by the \l{addApplicationFallbackFontFamily()} function.
+
+ \sa setApplicationFallbackFontFamilies(), addApplicationFallbackFontFamily(), removeApplicationFallbackFontFamily()
+*/
+QStringList QFontDatabase::applicationFallbackFontFamilies(QChar::Script script)
+{
+ QMutexLocker locker(fontDatabaseMutex());
+
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
+ auto *db = QFontDatabasePrivate::instance();
+ return db->applicationFallbackFontFamilies.value(script);
+}
+
+/*!
\internal
*/
QFontEngine *QFontDatabasePrivate::findFont(const QFontDef &req,
@@ -2465,7 +2611,7 @@ QFontEngine *QFontDatabasePrivate::findFont(const QFontDef &req,
if (!engine) {
QtFontDesc desc;
do {
- index = match(multi ? QChar::Script_Common : script, def, def.families.first(), ""_L1, &desc, blackListed);
+ index = match(multi ? QChar::Script_Common : script, def, def.families.constFirst(), ""_L1, &desc, blackListed);
if (index >= 0) {
QFontDef loadDef = def;
if (loadDef.families.isEmpty())
@@ -2541,7 +2687,7 @@ void QFontDatabasePrivate::load(const QFontPrivate *d, int script)
family_list << req.families.at(0);
// add the default family
- auto families = QGuiApplication::font().families();
+ const auto families = QGuiApplication::font().families();
if (!families.isEmpty()) {
QString defaultFamily = families.first();
if (! family_list.contains(defaultFamily))
diff --git a/src/gui/text/qfontdatabase.h b/src/gui/text/qfontdatabase.h
index c66451a164..91a534265e 100644
--- a/src/gui/text/qfontdatabase.h
+++ b/src/gui/text/qfontdatabase.h
@@ -112,6 +112,11 @@ public:
static bool removeApplicationFont(int id);
static bool removeAllApplicationFonts();
+ static void addApplicationFallbackFontFamily(QChar::Script script, const QString &familyName);
+ static bool removeApplicationFallbackFontFamily(QChar::Script script, const QString &familyName);
+ static void setApplicationFallbackFontFamilies(QChar::Script, const QStringList &familyNames);
+ static QStringList applicationFallbackFontFamilies(QChar::Script script);
+
static QFont systemFont(SystemFont type);
};
diff --git a/src/gui/text/qfontdatabase_p.h b/src/gui/text/qfontdatabase_p.h
index a0796d25c0..38e1b4ad20 100644
--- a/src/gui/text/qfontdatabase_p.h
+++ b/src/gui/text/qfontdatabase_p.h
@@ -204,6 +204,8 @@ public:
QtFontFamily **families;
bool populated = false;
+ QHash<QChar::Script, QStringList> applicationFallbackFontFamilies;
+
QCache<QtFontFallbacksCacheKey, QStringList> fallbacksCache;
struct ApplicationFont {
QString fileName;
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
index 5035b61fe9..4e78aaac2e 100644
--- a/src/gui/text/qfontengine.cpp
+++ b/src/gui/text/qfontengine.cpp
@@ -13,6 +13,7 @@
#include "qpainter.h"
#include "qpainterpath.h"
#include "qvarlengtharray.h"
+#include "qtextengine_p.h"
#include <qmath.h>
#include <qendian.h>
#include <private/qstringiterator_p.h>
@@ -182,8 +183,10 @@ bool QFontEngine::supportsScript(QChar::Script script) const
#if QT_CONFIG(harfbuzz)
// in AAT fonts, 'gsub' table is effectively replaced by 'mort'/'morx' table
uint lenMort = 0, lenMorx = 0;
- if (getSfntTableData(MAKE_TAG('m','o','r','t'), nullptr, &lenMort) || getSfntTableData(MAKE_TAG('m','o','r','x'), nullptr, &lenMorx))
+ if (getSfntTableData(QFont::Tag("mort").value(), nullptr, &lenMort)
+ || getSfntTableData(QFont::Tag("morx").value(), nullptr, &lenMorx)) {
return true;
+ }
if (hb_face_t *face = hb_qt_face_get_for_engine(const_cast<QFontEngine *>(this))) {
unsigned int script_count = HB_OT_MAX_TAGS_PER_SCRIPT;
@@ -381,7 +384,7 @@ void QFontEngine::getGlyphBearings(glyph_t glyph, qreal *leftBearing, qreal *rig
bool QFontEngine::processHheaTable() const
{
- QByteArray hhea = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a'));
+ QByteArray hhea = getSfntTable(QFont::Tag("hhea").value());
if (hhea.size() >= 10) {
auto ptr = hhea.constData();
qint16 ascent = qFromBigEndian<qint16>(ptr + 4);
@@ -407,9 +410,9 @@ bool QFontEngine::processHheaTable() const
void QFontEngine::initializeHeightMetrics() const
{
bool hasEmbeddedBitmaps =
- !getSfntTable(MAKE_TAG('E', 'B', 'L', 'C')).isEmpty()
- || !getSfntTable(MAKE_TAG('C', 'B', 'L', 'C')).isEmpty()
- || !getSfntTable(MAKE_TAG('b', 'd', 'a', 't')).isEmpty();
+ !getSfntTable(QFont::Tag("EBLC").value()).isEmpty()
+ || !getSfntTable(QFont::Tag("CBLC").value()).isEmpty()
+ || !getSfntTable(QFont::Tag("bdat").value()).isEmpty();
if (!hasEmbeddedBitmaps) {
// Get HHEA table values if available
processHheaTable();
@@ -429,7 +432,7 @@ void QFontEngine::initializeHeightMetrics() const
bool QFontEngine::processOS2Table() const
{
- QByteArray os2 = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
+ QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value());
if (os2.size() >= 78) {
auto ptr = os2.constData();
quint16 fsSelection = qFromBigEndian<quint16>(ptr + 62);
@@ -505,7 +508,7 @@ qreal QFontEngine::minRightBearing() const
if (m_minRightBearing == kBearingNotInitialized) {
// Try the 'hhea' font table first, which covers the entire font
- QByteArray hheaTable = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a'));
+ QByteArray hheaTable = getSfntTable(QFont::Tag("hhea").value());
if (hheaTable.size() >= int(kMinRightSideBearingOffset + sizeof(qint16))) {
const uchar *tableData = reinterpret_cast<const uchar *>(hheaTable.constData());
Q_ASSERT(q16Dot16ToFloat(qFromBigEndian<quint32>(tableData)) == 1.0);
@@ -1045,7 +1048,7 @@ void QFontEngine::loadKerningPairs(QFixed scalingFactor)
{
kerning_pairs.clear();
- QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n'));
+ QByteArray tab = getSfntTable(QFont::Tag("kern").value());
if (tab.isEmpty())
return;
@@ -1134,7 +1137,7 @@ end:
int QFontEngine::glyphCount() const
{
- QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p'));
+ QByteArray maxpTable = getSfntTable(QFont::Tag("maxp").value());
if (maxpTable.size() < 6)
return 0;
@@ -1181,7 +1184,7 @@ const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSy
int tableToUse = -1;
int score = Invalid;
for (int n = 0; n < numTables; ++n) {
- quint16 platformId;
+ quint16 platformId = 0;
if (!qSafeFromBigEndian(maps + 8 * n, endPtr, &platformId))
return nullptr;
@@ -1232,6 +1235,7 @@ const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSy
default:
break;
}
+ break;
default:
break;
}
@@ -1311,7 +1315,7 @@ resolveTable:
quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint unicode)
{
const uchar *end = cmap + cmapSize;
- quint16 format;
+ quint16 format = 0;
if (!qSafeFromBigEndian(cmap, end, &format))
return 0;
@@ -1328,7 +1332,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
if (unicode >= 0xffff)
return 0;
- quint16 segCountX2;
+ quint16 segCountX2 = 0;
if (!qSafeFromBigEndian(cmap + 6, end, &segCountX2))
return 0;
@@ -1336,7 +1340,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
int i = 0;
for (; i < segCountX2/2; ++i) {
- quint16 codePoint;
+ quint16 codePoint = 0;
if (!qSafeFromBigEndian(ends + 2 * i, end, &codePoint))
return 0;
if (codePoint >= unicode)
@@ -1345,7 +1349,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
const unsigned char *idx = ends + segCountX2 + 2 + 2*i;
- quint16 startIndex;
+ quint16 startIndex = 0;
if (!qSafeFromBigEndian(idx, end, &startIndex))
return 0;
if (startIndex > unicode)
@@ -1353,20 +1357,20 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
idx += segCountX2;
- quint16 tmp;
+ quint16 tmp = 0;
if (!qSafeFromBigEndian(idx, end, &tmp))
return 0;
qint16 idDelta = qint16(tmp);
idx += segCountX2;
- quint16 idRangeoffset_t;
+ quint16 idRangeoffset_t = 0;
if (!qSafeFromBigEndian(idx, end, &idRangeoffset_t))
return 0;
- quint16 glyphIndex;
+ quint16 glyphIndex = 0;
if (idRangeoffset_t) {
- quint16 id;
+ quint16 id = 0;
if (!qSafeFromBigEndian(idRangeoffset_t + 2 * (unicode - startIndex) + idx, end, &id))
return 0;
@@ -1379,17 +1383,17 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
}
return glyphIndex;
} else if (format == 6) {
- quint16 tableSize;
+ quint16 tableSize = 0;
if (!qSafeFromBigEndian(cmap + 2, end, &tableSize))
return 0;
- quint16 firstCode6;
+ quint16 firstCode6 = 0;
if (!qSafeFromBigEndian(cmap + 6, end, &firstCode6))
return 0;
if (unicode < firstCode6)
return 0;
- quint16 entryCount6;
+ quint16 entryCount6 = 0;
if (!qSafeFromBigEndian(cmap + 8, end, &entryCount6))
return 0;
if (entryCount6 * 2 + 10 > tableSize)
@@ -1405,7 +1409,7 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
qSafeFromBigEndian(cmap + 10 + (entryIndex6 * 2), end, &index);
return index;
} else if (format == 12) {
- quint32 nGroups;
+ quint32 nGroups = 0;
if (!qSafeFromBigEndian(cmap + 12, end, &nGroups))
return 0;
@@ -1415,19 +1419,19 @@ quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, int cmapSize, uint
while (left <= right) {
int middle = left + ( ( right - left ) >> 1 );
- quint32 startCharCode;
+ quint32 startCharCode = 0;
if (!qSafeFromBigEndian(cmap + 12 * middle, end, &startCharCode))
return 0;
if (unicode < startCharCode)
right = middle - 1;
else {
- quint32 endCharCode;
+ quint32 endCharCode = 0;
if (!qSafeFromBigEndian(cmap + 12 * middle + 4, end, &endCharCode))
return 0;
if (unicode <= endCharCode) {
- quint32 index;
+ quint32 index = 0;
if (!qSafeFromBigEndian(cmap + 12 * middle + 8, end, &index))
return 0;
@@ -1471,10 +1475,10 @@ bool QFontEngine::hasUnreliableGlyphOutline() const
QFixed QFontEngine::firstLeftBearing(const QGlyphLayout &glyphs)
{
- if (glyphs.numGlyphs >= 1) {
- glyph_t glyph = glyphs.glyphs[0];
+ for (int i = 0; i < glyphs.numGlyphs; ++i) {
+ glyph_t glyph = glyphs.glyphs[i];
glyph_metrics_t gi = boundingBox(glyph);
- if (gi.isValid())
+ if (gi.isValid() && gi.width > 0)
return gi.leftBearing();
}
return 0;
@@ -1539,12 +1543,12 @@ glyph_t QFontEngineBox::glyphIndex(uint ucs4) const
return 1;
}
-bool QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const
+int QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const
{
Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
if (*nglyphs < len) {
*nglyphs = len;
- return false;
+ return -1;
}
int ucs4Length = 0;
@@ -1560,7 +1564,7 @@ bool QFontEngineBox::stringToCMap(const QChar *str, int len, QGlyphLayout *glyph
if (!(flags & GlyphIndicesOnly))
recalcAdvances(glyphs, flags);
- return true;
+ return *nglyphs;
}
void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags) const
@@ -1727,7 +1731,7 @@ void QFontEngineMulti::ensureFallbackFamiliesQueried()
if (styleHint == QFont::AnyStyle && fontDef.fixedPitch)
styleHint = QFont::TypeWriter;
- setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.families.first(),
+ setFallbackFamiliesList(qt_fallbacksForFamily(fontDef.families.constFirst(),
QFont::Style(fontDef.style), styleHint,
QChar::Script(m_script)));
}
@@ -1743,7 +1747,7 @@ void QFontEngineMulti::setFallbackFamiliesList(const QStringList &fallbackFamili
QFontEngine *engine = m_engines.at(0);
engine->ref.ref();
m_engines[1] = engine;
- m_fallbackFamilies << fontDef.families.first();
+ m_fallbackFamilies << fontDef.families.constFirst();
} else {
m_engines.resize(m_fallbackFamilies.size() + 1);
}
@@ -1790,11 +1794,7 @@ QFontEngine *QFontEngineMulti::loadEngine(int at)
glyph_t QFontEngineMulti::glyphIndex(uint ucs4) const
{
glyph_t glyph = engine(0)->glyphIndex(ucs4);
- if (glyph == 0
- && ucs4 != QChar::LineSeparator
- && ucs4 != QChar::LineFeed
- && ucs4 != QChar::CarriageReturn
- && ucs4 != QChar::ParagraphSeparator) {
+ if (glyph == 0 && !isIgnorableChar(ucs4)) {
if (!m_fallbackFamiliesQueried)
const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried();
for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) {
@@ -1821,13 +1821,55 @@ glyph_t QFontEngineMulti::glyphIndex(uint ucs4) const
return glyph;
}
-bool QFontEngineMulti::stringToCMap(const QChar *str, int len,
- QGlyphLayout *glyphs, int *nglyphs,
- QFontEngine::ShaperFlags flags) const
+int QFontEngineMulti::stringToCMap(const QChar *str, int len,
+ QGlyphLayout *glyphs, int *nglyphs,
+ QFontEngine::ShaperFlags flags) const
{
- if (!engine(0)->stringToCMap(str, len, glyphs, nglyphs, flags))
- return false;
+ const int originalNumGlyphs = glyphs->numGlyphs;
+ int mappedGlyphCount = engine(0)->stringToCMap(str, len, glyphs, nglyphs, flags);
+ if (mappedGlyphCount < 0)
+ return -1;
+
+ // If ContextFontMerging is set and the match for the string was incomplete, we try all
+ // fallbacks on the full string until we find the best match.
+ bool contextFontMerging = mappedGlyphCount < *nglyphs && (fontDef.styleStrategy & QFont::ContextFontMerging);
+ if (contextFontMerging) {
+ QVarLengthGlyphLayoutArray tempLayout(len);
+ if (!m_fallbackFamiliesQueried)
+ const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried();
+
+ int maxGlyphCount = 0;
+ uchar engineIndex = 0;
+ for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) {
+ int numGlyphs = len;
+ const_cast<QFontEngineMulti *>(this)->ensureEngineAt(x);
+ maxGlyphCount = engine(x)->stringToCMap(str, len, &tempLayout, &numGlyphs, flags);
+
+ // If we found a better match, we copy data into the main QGlyphLayout
+ if (maxGlyphCount > mappedGlyphCount) {
+ *nglyphs = numGlyphs;
+ glyphs->numGlyphs = originalNumGlyphs;
+ glyphs->copy(&tempLayout);
+ engineIndex = x;
+ if (maxGlyphCount == numGlyphs)
+ break;
+ }
+ }
+ if (engineIndex > 0) {
+ for (int y = 0; y < glyphs->numGlyphs; ++y) {
+ if (glyphs->glyphs[y] != 0)
+ glyphs->glyphs[y] |= (engineIndex << 24);
+ }
+ } else {
+ contextFontMerging = false;
+ }
+
+ mappedGlyphCount = maxGlyphCount;
+ }
+
+ // Fill in missing glyphs by going through string one character at the time and finding
+ // the first viable fallback.
int glyph_pos = 0;
QStringIterator it(str, str + len);
@@ -1858,15 +1900,10 @@ bool QFontEngineMulti::stringToCMap(const QChar *str, int len,
lastFallback = -1;
}
- if (glyphs->glyphs[glyph_pos] == 0
- && ucs4 != QChar::LineSeparator
- && ucs4 != QChar::LineFeed
- && ucs4 != QChar::CarriageReturn
- && ucs4 != QChar::ParagraphSeparator
- && QChar::category(ucs4) != QChar::Other_PrivateUse) {
+ if (glyphs->glyphs[glyph_pos] == 0 && !isIgnorableChar(ucs4)) {
if (!m_fallbackFamiliesQueried)
const_cast<QFontEngineMulti *>(this)->ensureFallbackFamiliesQueried();
- for (int x = 1, n = qMin(m_engines.size(), 256); x < n; ++x) {
+ for (int x = contextFontMerging ? 0 : 1, n = qMin(m_engines.size(), 256); x < n; ++x) {
QFontEngine *engine = m_engines.at(x);
if (!engine) {
if (!shouldLoadFontEngineForCharacter(x, ucs4))
@@ -1905,17 +1942,32 @@ bool QFontEngineMulti::stringToCMap(const QChar *str, int len,
int precedingCharacterFontEngine = glyphs->glyphs[glyph_pos - 1] >> 24;
if (selectorFontEngine != precedingCharacterFontEngine) {
- QFontEngine *engine = m_engines.at(selectorFontEngine);
- glyph_t glyph = engine->glyphIndex(previousUcs4);
- if (glyph != 0) {
- glyphs->glyphs[glyph_pos - 1] = glyph;
- if (!(flags & GlyphIndicesOnly)) {
- QGlyphLayout g = glyphs->mid(glyph_pos - 1, 1);
- engine->recalcAdvances(&g, flags);
+ // Emoji variant selectors are specially handled and should affect font
+ // selection. If VS-16 is used, then this means we want to select a color
+ // font. If the selected font is already a color font, we do not need search
+ // again. If the VS-15 is used, then this means we want to select a non-color
+ // font. If the selected font is not a color font, we don't do anything.
+ const QFontEngine *selectedEngine = m_engines.at(precedingCharacterFontEngine);
+ const bool colorFont = selectedEngine->isColorFont();
+ const char32_t vs15 = 0xFE0E;
+ const char32_t vs16 = 0xFE0F;
+ bool adaptVariantSelector = ucs4 < vs15
+ || (ucs4 == vs15 && colorFont)
+ || (ucs4 == vs16 && !colorFont);
+
+ if (adaptVariantSelector) {
+ QFontEngine *engine = m_engines.at(selectorFontEngine);
+ glyph_t glyph = engine->glyphIndex(previousUcs4);
+ if (glyph != 0) {
+ glyphs->glyphs[glyph_pos - 1] = glyph;
+ if (!(flags & GlyphIndicesOnly)) {
+ QGlyphLayout g = glyphs->mid(glyph_pos - 1, 1);
+ engine->recalcAdvances(&g, flags);
+ }
+
+ // set the high byte to indicate which engine the glyph came from
+ glyphs->glyphs[glyph_pos - 1] |= (selectorFontEngine << 24);
}
-
- // set the high byte to indicate which engine the glyph came from
- glyphs->glyphs[glyph_pos - 1] |= (selectorFontEngine << 24);
}
}
}
@@ -1928,8 +1980,7 @@ bool QFontEngineMulti::stringToCMap(const QChar *str, int len,
*nglyphs = glyph_pos;
glyphs->numGlyphs = glyph_pos;
-
- return true;
+ return mappedGlyphCount;
}
bool QFontEngineMulti::shouldLoadFontEngineForCharacter(int at, uint ucs4) const
@@ -2221,7 +2272,7 @@ bool QFontEngineMulti::canRender(const QChar *string, int len) const
QGlyphLayout g;
g.numGlyphs = nglyphs;
g.glyphs = glyphs.data();
- if (!stringToCMap(string, len, &g, &nglyphs, GlyphIndicesOnly))
+ if (stringToCMap(string, len, &g, &nglyphs, GlyphIndicesOnly) < 0)
Q_UNREACHABLE();
for (int i = 0; i < nglyphs; i++) {
diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h
index dbad0d95f9..a0e0801354 100644
--- a/src/gui/text/qfontengine_p.h
+++ b/src/gui/text/qfontengine_p.h
@@ -29,13 +29,6 @@ class QFontEngineGlyphCache;
struct QGlyphLayout;
-#define MAKE_TAG(ch1, ch2, ch3, ch4) (\
- (((quint32)(ch1)) << 24) | \
- (((quint32)(ch2)) << 16) | \
- (((quint32)(ch3)) << 8) | \
- ((quint32)(ch4)) \
- )
-
// ### this only used in getPointInOutline(), refactor it and then remove these magic numbers
enum HB_Compat_Error {
Err_Ok = 0x0000,
@@ -83,7 +76,8 @@ public:
enum ShaperFlag {
DesignMetrics = 0x0002,
- GlyphIndicesOnly = 0x0004
+ GlyphIndicesOnly = 0x0004,
+ FullStringFallback = 0x008
};
Q_DECLARE_FLAGS(ShaperFlags, ShaperFlag)
@@ -127,11 +121,13 @@ public:
virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const;
struct FaceId {
- FaceId() : index(0), encoding(0) {}
+ FaceId() : index(0), instanceIndex(-1), encoding(0) {}
QByteArray filename;
QByteArray uuid;
int index;
+ int instanceIndex;
int encoding;
+ QMap<QFont::Tag, float> variableAxes;
};
virtual FaceId faceId() const { return FaceId(); }
enum SynthesizedFlags {
@@ -152,11 +148,21 @@ public:
return subPixelPositionFor(QFixedPoint(x, 0)).x;
}
+ bool isColorFont() const { return glyphFormat == Format_ARGB; }
+ static bool isIgnorableChar(char32_t ucs4)
+ {
+ return ucs4 == QChar::LineSeparator
+ || ucs4 == QChar::LineFeed
+ || ucs4 == QChar::CarriageReturn
+ || ucs4 == QChar::ParagraphSeparator
+ || QChar::category(ucs4) == QChar::Other_Control;
+ }
+
virtual QFixed emSquareSize() const { return ascent(); }
/* returns 0 as glyph index for non existent glyphs */
virtual glyph_t glyphIndex(uint ucs4) const = 0;
- virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const = 0;
+ virtual int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const = 0;
virtual void recalcAdvances(QGlyphLayout *, ShaperFlags) const {}
virtual void doKerning(QGlyphLayout *, ShaperFlags) const;
@@ -370,13 +376,18 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QFontEngine::ShaperFlags)
inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId &f2)
{
- return f1.index == f2.index && f1.encoding == f2.encoding && f1.filename == f2.filename && f1.uuid == f2.uuid;
+ return f1.index == f2.index
+ && f1.encoding == f2.encoding
+ && f1.filename == f2.filename
+ && f1.uuid == f2.uuid
+ && f1.instanceIndex == f2.instanceIndex
+ && f1.variableAxes == f2.variableAxes;
}
inline size_t qHash(const QFontEngine::FaceId &f, size_t seed = 0)
noexcept(noexcept(qHash(f.filename)))
{
- return qHashMulti(seed, f.filename, f.uuid, f.index, f.encoding);
+ return qHashMulti(seed, f.filename, f.uuid, f.index, f.instanceIndex, f.encoding, f.variableAxes.keys(), f.variableAxes.values());
}
@@ -391,7 +402,7 @@ public:
~QFontEngineBox();
virtual glyph_t glyphIndex(uint ucs4) const override;
- virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ virtual int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
virtual void recalcAdvances(QGlyphLayout *, ShaperFlags) const override;
void draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &si);
@@ -429,7 +440,7 @@ public:
~QFontEngineMulti();
virtual glyph_t glyphIndex(uint ucs4) const override;
- virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ virtual int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) override;
virtual glyph_metrics_t boundingBox(glyph_t glyph) override;
diff --git a/src/gui/text/qfontinfo.h b/src/gui/text/qfontinfo.h
index c162003801..0edee5abe5 100644
--- a/src/gui/text/qfontinfo.h
+++ b/src/gui/text/qfontinfo.h
@@ -7,6 +7,8 @@
#include <QtGui/qtguiglobal.h>
#include <QtGui/qfont.h>
+#include <QtCore/qshareddata.h>
+
QT_BEGIN_NAMESPACE
diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp
index cd0886440b..f7e405f0b5 100644
--- a/src/gui/text/qfontmetrics.cpp
+++ b/src/gui/text/qfontmetrics.cpp
@@ -91,7 +91,7 @@ extern void qt_format_text(const QFont& font, const QRectF &_r,
Example:
\snippet code/src_gui_text_qfontmetrics.cpp 0
- \sa QFont, QFontInfo, QFontDatabase, {Character Map Example}
+ \sa QFont, QFontInfo, QFontDatabase
*/
/*!
diff --git a/src/gui/text/qfontmetrics.h b/src/gui/text/qfontmetrics.h
index 8f16ccb5a7..1942d1fa83 100644
--- a/src/gui/text/qfontmetrics.h
+++ b/src/gui/text/qfontmetrics.h
@@ -6,9 +6,11 @@
#include <QtGui/qtguiglobal.h>
#include <QtGui/qfont.h>
+
#ifndef QT_INCLUDE_COMPAT
#include <QtCore/qrect.h>
#endif
+#include <QtCore/qshareddata.h>
QT_BEGIN_NAMESPACE
diff --git a/src/gui/text/qfontsubset.cpp b/src/gui/text/qfontsubset.cpp
index 1f5ffac8d4..f6c973e522 100644
--- a/src/gui/text/qfontsubset.cpp
+++ b/src/gui/text/qfontsubset.cpp
@@ -401,7 +401,7 @@ static QTtfTable generateHead(const qttf_head_table &head)
{
const int head_size = 54;
QTtfTable t;
- t.tag = MAKE_TAG('h', 'e', 'a', 'd');
+ t.tag = QFont::Tag("head").value();
t.data.resize(head_size);
QTtfStream s(t.data);
@@ -472,7 +472,7 @@ static QTtfTable generateHhea(const qttf_hhea_table &hhea)
{
const int hhea_size = 36;
QTtfTable t;
- t.tag = MAKE_TAG('h', 'h', 'e', 'a');
+ t.tag = QFont::Tag("hhea").value();
t.data.resize(hhea_size);
QTtfStream s(t.data);
@@ -523,7 +523,7 @@ static QTtfTable generateMaxp(const qttf_maxp_table &maxp)
{
const int maxp_size = 32;
QTtfTable t;
- t.tag = MAKE_TAG('m', 'a', 'x', 'p');
+ t.tag = QFont::Tag("maxp").value();
t.data.resize(maxp_size);
QTtfStream s(t.data);
@@ -603,7 +603,7 @@ static QTtfTable generateName(const QList<QTtfNameRecord> &name)
const int char_size = 2;
QTtfTable t;
- t.tag = MAKE_TAG('n', 'a', 'm', 'e');
+ t.tag = QFont::Tag("name").value();
const int name_size = 6 + 12*name.size();
int string_size = 0;
@@ -958,15 +958,15 @@ static QList<QTtfTable> generateGlyphTables(qttf_font_tables &tables, const QLis
tables.hhea.numberOfHMetrics = nGlyphs;
QTtfTable glyf;
- glyf.tag = MAKE_TAG('g', 'l', 'y', 'f');
+ glyf.tag = QFont::Tag("glyf").value();
QTtfTable loca;
- loca.tag = MAKE_TAG('l', 'o', 'c', 'a');
+ loca.tag = QFont::Tag("loca").value();
loca.data.resize(glyf_size < max_size_small ? (nGlyphs+1)*sizeof(quint16) : (nGlyphs+1)*sizeof(quint32));
QTtfStream ls(loca.data);
QTtfTable hmtx;
- hmtx.tag = MAKE_TAG('h', 'm', 't', 'x');
+ hmtx.tag = QFont::Tag("hmtx").value();
hmtx.data.resize(nGlyphs*4);
QTtfStream hs(hmtx.data);
@@ -1066,7 +1066,7 @@ static QByteArray bindFont(const QList<QTtfTable>& _tables)
for (int i = 0; i < tables.size(); ++i) {
const QTtfTable &t = tables.at(i);
const quint32 size = (t.data.size() + 3) & ~3;
- if (t.tag == MAKE_TAG('h', 'e', 'a', 'd'))
+ if (t.tag == QFont::Tag("head").value())
head_offset = table_offset;
f << t.tag
<< checksum(t.data)
@@ -1186,7 +1186,7 @@ QByteArray QFontSubset::toTruetype() const
tables.append(generateMaxp(font.maxp));
// name
QTtfTable name_table;
- name_table.tag = MAKE_TAG('n', 'a', 'm', 'e');
+ name_table.tag = QFont::Tag("name").value();
if (!noEmbed)
name_table.data = fontEngine->getSfntTable(name_table.tag);
if (name_table.data.isEmpty()) {
@@ -1195,7 +1195,7 @@ QByteArray QFontSubset::toTruetype() const
name.copyright = "Fake font"_L1;
else
name.copyright = QLatin1StringView(properties.copyright);
- name.family = fontEngine->fontDef.families.first();
+ name.family = fontEngine->fontDef.families.constFirst();
name.subfamily = "Regular"_L1; // ######
name.postscript_name = QLatin1StringView(properties.postscriptName);
name_table = generateName(name);
@@ -1204,7 +1204,7 @@ QByteArray QFontSubset::toTruetype() const
if (!noEmbed) {
QTtfTable os2;
- os2.tag = MAKE_TAG('O', 'S', '/', '2');
+ os2.tag = QFont::Tag("OS/2").value();
os2.data = fontEngine->getSfntTable(os2.tag);
if (!os2.data.isEmpty())
tables.append(os2);
diff --git a/src/gui/text/qplatformfontdatabase.cpp b/src/gui/text/qplatformfontdatabase.cpp
index ce7713db03..a146254f68 100644
--- a/src/gui/text/qplatformfontdatabase.cpp
+++ b/src/gui/text/qplatformfontdatabase.cpp
@@ -646,6 +646,18 @@ bool QPlatformFontDatabase::isFamilyPopulated(const QString &familyName)
}
/*!
+ Returns true if this font database supports loading named instances from variable application
+ fonts.
+
+ \since 6.7
+*/
+bool QPlatformFontDatabase::supportsVariableApplicationFonts() const
+{
+ return false;
+}
+
+
+/*!
\class QPlatformFontDatabase
\since 5.0
\internal
diff --git a/src/gui/text/qplatformfontdatabase.h b/src/gui/text/qplatformfontdatabase.h
index a5e65086a8..3007a11838 100644
--- a/src/gui/text/qplatformfontdatabase.h
+++ b/src/gui/text/qplatformfontdatabase.h
@@ -89,6 +89,8 @@ public:
virtual bool fontsAlwaysScalable() const;
virtual QList<int> standardSizes() const;
+ virtual bool supportsVariableApplicationFonts() const;
+
// helper
static QSupportedWritingSystems writingSystemsFromTrueTypeBits(quint32 unicodeRange[4], quint32 codePageRange[2]);
static QSupportedWritingSystems writingSystemsFromOS2Table(const char *os2Table, size_t length);
diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp
index 5b4f5d6b1b..54676b3560 100644
--- a/src/gui/text/qrawfont.cpp
+++ b/src/gui/text/qrawfont.cpp
@@ -440,7 +440,7 @@ qreal QRawFont::underlinePosition() const
*/
QString QRawFont::familyName() const
{
- return d->isValid() ? d->fontEngine->fontDef.families.first() : QString();
+ return d->isValid() ? d->fontEngine->fontDef.families.constFirst() : QString();
}
/*!
@@ -498,7 +498,7 @@ QList<quint32> QRawFont::glyphIndexesForString(const QString &text) const
QGlyphLayout glyphs;
glyphs.numGlyphs = numGlyphs;
glyphs.glyphs = glyphIndexes.data();
- if (!d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &numGlyphs, QFontEngine::GlyphIndicesOnly))
+ if (d->fontEngine->stringToCMap(text.data(), text.size(), &glyphs, &numGlyphs, QFontEngine::GlyphIndicesOnly) < 0)
Q_UNREACHABLE();
glyphIndexes.resize(numGlyphs);
@@ -531,7 +531,7 @@ bool QRawFont::glyphIndexesForChars(const QChar *chars, int numChars, quint32 *g
QGlyphLayout glyphs;
glyphs.numGlyphs = *numGlyphs;
glyphs.glyphs = glyphIndexes;
- return d->fontEngine->stringToCMap(chars, numChars, &glyphs, numGlyphs, QFontEngine::GlyphIndicesOnly);
+ return d->fontEngine->stringToCMap(chars, numChars, &glyphs, numGlyphs, QFontEngine::GlyphIndicesOnly) >= 0;
}
/*!
@@ -632,17 +632,33 @@ QFont::HintingPreference QRawFont::hintingPreference() const
}
/*!
- Retrieves the sfnt table named \a tagName from the underlying physical font, or an empty
- byte array if no such table was found. The returned font table's byte order is Big Endian, like
- the sfnt format specifies. The \a tagName must be four characters long and should be formatted
- in the default endianness of the current platform.
+ \fn QByteArray QRawFont::fontTable(const char *tag) const
+ \overload fontTable(QFont::Tag)
+
+ The name must be a four-character string.
+*/
+
+/*!
+ \fn QByteArray QRawFont::fontTable(QFont::Tag tag) const
+ \since 6.7
+
+ Retrieves the sfnt table specified by \a tag from the underlying physical font,
+ or an empty byte array if no such table was found. The returned font table's byte order is
+ Big Endian, like the sfnt format specifies.
*/
-QByteArray QRawFont::fontTable(const char *tagName) const
+QByteArray QRawFont::fontTable(const char *tag) const
+{
+ if (auto maybeTag = QFont::Tag::fromString(tag))
+ return fontTable(*maybeTag);
+ return QByteArray();
+}
+
+QByteArray QRawFont::fontTable(QFont::Tag tag) const
{
if (!d->isValid())
return QByteArray();
- return d->fontEngine->getSfntTable(MAKE_TAG(tagName[0], tagName[1], tagName[2], tagName[3]));
+ return d->fontEngine->getSfntTable(tag.value());
}
/*!
diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h
index ca202d897f..d23d0c1493 100644
--- a/src/gui/text/qrawfont.h
+++ b/src/gui/text/qrawfont.h
@@ -105,6 +105,7 @@ public:
QList<QFontDatabase::WritingSystem> supportedWritingSystems() const;
QByteArray fontTable(const char *tagName) const;
+ QByteArray fontTable(QFont::Tag tag) const;
static QRawFont fromFont(const QFont &font,
QFontDatabase::WritingSystem writingSystem = QFontDatabase::Any);
diff --git a/src/gui/text/qtextcursor.cpp b/src/gui/text/qtextcursor.cpp
index c23bcf0317..5730f55e6a 100644
--- a/src/gui/text/qtextcursor.cpp
+++ b/src/gui/text/qtextcursor.cpp
@@ -1679,7 +1679,7 @@ static void getText(QString &text, QTextDocumentPrivate *priv, const QString &do
const int offsetInFragment = qMax(0, pos - fragIt.position());
const int len = qMin(int(frag->size_array[0] - offsetInFragment), end - pos);
- text += QString(docText.constData() + frag->stringPosition + offsetInFragment, len);
+ text += QStringView(docText.constData() + frag->stringPosition + offsetInFragment, len);
pos += len;
}
}
@@ -2300,7 +2300,7 @@ void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::
d->priv->beginEditBlock();
d->remove();
const int idx = d->priv->formatCollection()->indexForFormat(fmt);
- d->priv->insert(d->position, QString(QChar(QChar::ObjectReplacementCharacter)), idx);
+ d->priv->insert(d->position, QChar(QChar::ObjectReplacementCharacter), idx);
d->priv->endEditBlock();
}
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
index e5dc5136cb..c39d3514c5 100644
--- a/src/gui/text/qtextdocument.cpp
+++ b/src/gui/text/qtextdocument.cpp
@@ -11,6 +11,7 @@
#include "qtexttable.h"
#include "qtextlist.h"
#include <qdebug.h>
+#include <qloggingcategory.h>
#if QT_CONFIG(regularexpression)
#include <qregularexpression.h>
#endif
@@ -42,6 +43,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcLayout);
+
using namespace Qt::StringLiterals;
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION unsigned int qt_int_sqrt(unsigned int n);
@@ -50,7 +53,12 @@ namespace {
QTextDocument::ResourceProvider qt_defaultResourceProvider;
};
+QAbstractUndoItem::~QAbstractUndoItem()
+ = default;
+
/*!
+ \fn bool Qt::mightBeRichText(QAnyStringView text)
+
Returns \c true if the string \a text is likely to be rich text;
otherwise returns \c false.
@@ -60,18 +68,21 @@ namespace {
for common cases, there is no guarantee.
This function is defined in the \c <QTextDocument> header file.
-*/
-bool Qt::mightBeRichText(const QString& text)
+
+ \note In Qt versions prior to 6.7, this function took QString only.
+ */
+template <typename T>
+static bool mightBeRichTextImpl(T text)
{
if (text.isEmpty())
return false;
- int start = 0;
+ qsizetype start = 0;
- while (start < text.size() && text.at(start).isSpace())
+ while (start < text.size() && QChar(text.at(start)).isSpace())
++start;
// skip a leading <?xml ... ?> as for example with xhtml
- if (QStringView{text}.mid(start, 5).compare("<?xml"_L1) == 0) {
+ if (text.mid(start, 5).compare("<?xml"_L1) == 0) {
while (start < text.size()) {
if (text.at(start) == u'?'
&& start + 2 < text.size()
@@ -82,35 +93,36 @@ bool Qt::mightBeRichText(const QString& text)
++start;
}
- while (start < text.size() && text.at(start).isSpace())
+ while (start < text.size() && QChar(text.at(start)).isSpace())
++start;
}
- if (QStringView{text}.mid(start, 5).compare("<!doc"_L1, Qt::CaseInsensitive) == 0)
+ if (text.mid(start, 5).compare("<!doc"_L1, Qt::CaseInsensitive) == 0)
return true;
- int open = start;
+ qsizetype open = start;
while (open < text.size() && text.at(open) != u'<'
&& text.at(open) != u'\n') {
- if (text.at(open) == u'&' && QStringView{text}.mid(open + 1, 3) == "lt;"_L1)
+ if (text.at(open) == u'&' && text.mid(open + 1, 3) == "lt;"_L1)
return true; // support desperate attempt of user to see <...>
++open;
}
if (open < text.size() && text.at(open) == u'<') {
- const int close = text.indexOf(u'>', open);
+ const qsizetype close = text.indexOf(u'>', open);
if (close > -1) {
- QString tag;
- for (int i = open+1; i < close; ++i) {
- if (text[i].isDigit() || text[i].isLetter())
- tag += text[i];
- else if (!tag.isEmpty() && text[i].isSpace())
+ QVarLengthArray<char16_t> tag;
+ for (qsizetype i = open + 1; i < close; ++i) {
+ const auto current = QChar(text[i]);
+ if (current.isDigit() || current.isLetter())
+ tag.append(current.toLower().unicode());
+ else if (!tag.isEmpty() && current.isSpace())
break;
- else if (!tag.isEmpty() && text[i] == u'/' && i + 1 == close)
+ else if (!tag.isEmpty() && current == u'/' && i + 1 == close)
break;
- else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != u'!'))
+ else if (!current.isSpace() && (!tag.isEmpty() || current != u'!'))
return false; // that's not a tag
}
#ifndef QT_NO_TEXTHTMLPARSER
- return QTextHtmlParser::lookupElement(std::move(tag).toLower()) != -1;
+ return QTextHtmlParser::lookupElement(tag) != -1;
#else
return false;
#endif // QT_NO_TEXTHTMLPARSER
@@ -119,6 +131,16 @@ bool Qt::mightBeRichText(const QString& text)
return false;
}
+static bool mightBeRichTextImpl(QUtf8StringView text)
+{
+ return mightBeRichTextImpl(QLatin1StringView(QByteArrayView(text)));
+}
+
+bool Qt::mightBeRichText(QAnyStringView text)
+{
+ return text.visit([](auto text) { return mightBeRichTextImpl(text); });
+}
+
/*!
Converts the plain text string \a plain to an HTML-formatted
paragraph while preserving most of its look.
@@ -131,12 +153,12 @@ bool Qt::mightBeRichText(const QString& text)
*/
QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
{
- int col = 0;
+ qsizetype col = 0;
QString rich;
rich += "<p>"_L1;
- for (int i = 0; i < plain.size(); ++i) {
+ for (qsizetype i = 0; i < plain.size(); ++i) {
if (plain[i] == u'\n'){
- int c = 1;
+ qsizetype c = 1;
while (i+1 < plain.size() && plain[i+1] == u'\n') {
i++;
c++;
@@ -235,7 +257,7 @@ QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
\li Text block group format changes.
\endlist
- \sa QTextCursor, QTextEdit, {Rich Text Processing}, {Text Object Example}
+ \sa QTextCursor, QTextEdit, {Rich Text Processing}
*/
/*!
@@ -711,6 +733,8 @@ void QTextDocument::setTextWidth(qreal width)
{
Q_D(QTextDocument);
QSizeF sz = d->pageSize;
+
+ qCDebug(lcLayout) << "page size" << sz << "-> width" << width;
sz.setWidth(width);
sz.setHeight(-1);
setPageSize(sz);
@@ -1138,6 +1162,8 @@ QString QTextDocument::metaInformation(MetaInformation info) const
return d->url;
case CssMedia:
return d->cssMedia;
+ case FrontMatter:
+ return d->frontMatter;
}
return QString();
}
@@ -1161,6 +1187,9 @@ void QTextDocument::setMetaInformation(MetaInformation info, const QString &stri
case CssMedia:
d->cssMedia = string;
break;
+ case FrontMatter:
+ d->frontMatter = string;
+ break;
}
}
@@ -1200,10 +1229,18 @@ QString QTextDocument::toPlainText() const
Q_D(const QTextDocument);
QString txt = d->plainText();
+ constexpr char16_t delims[] = { 0xfdd0, 0xfdd1,
+ QChar::ParagraphSeparator, QChar::LineSeparator, QChar::Nbsp };
+
+ const size_t pos = std::u16string_view(txt).find_first_of(
+ std::u16string_view(delims, std::size(delims)));
+ if (pos == std::u16string_view::npos)
+ return txt;
+
QChar *uc = txt.data();
- QChar *e = uc + txt.size();
+ QChar *const e = uc + txt.size();
- for (; uc != e; ++uc) {
+ for (uc += pos; uc != e; ++uc) {
switch (uc->unicode()) {
case 0xfdd0: // QTextBeginningOfFrame
case 0xfdd1: // QTextEndOfFrame
@@ -1267,6 +1304,8 @@ void QTextDocument::setHtml(const QString &html)
d->enableUndoRedo(false);
d->beginEditBlock();
d->clear();
+ // ctor calls parse() to build up QTextHtmlParser::nodes list
+ // then import() populates the QTextDocument from those
QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import();
d->endEditBlock();
d->enableUndoRedo(previousState);
@@ -1298,6 +1337,10 @@ void QTextDocument::setHtml(const QString &html)
\value CssMedia This value is used to select the corresponding '@media'
rule, if any, from a specified CSS stylesheet when setHtml()
is called. This enum value has been introduced in Qt 6.3.
+ \value FrontMatter This value is used to select header material, if any was
+ extracted during parsing of the source file (currently
+ only from Markdown format). This enum value has been
+ introduced in Qt 6.8.
\sa metaInformation(), setMetaInformation(), setHtml()
*/
@@ -1471,6 +1514,10 @@ static bool findInBlock(const QTextBlock &block, const QRegularExpression &expr,
If the \a from position is 0 (the default) the search begins from the beginning
of the document; otherwise it begins at the specified position.
+
+ \warning For historical reasons, the case sensitivity option set on
+ \a expr is ignored. Instead, the \a options are used to determine
+ if the search is case sensitive or not.
*/
QTextCursor QTextDocument::find(const QRegularExpression &expr, int from, FindFlags options) const
{
@@ -2366,11 +2413,14 @@ QString QTextHtmlExporter::toHtml(ExportMode mode)
fragmentMarkers = (mode == ExportFragment);
- html += QString::fromLatin1("<meta charset=\"utf-8\" />");
+ html += "<meta charset=\"utf-8\" />"_L1;
QString title = doc->metaInformation(QTextDocument::DocumentTitle);
- if (!title.isEmpty())
- html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>");
+ if (!title.isEmpty()) {
+ html += "<title>"_L1;
+ html += title;
+ html += "</title>"_L1;
+ }
html += "<style type=\"text/css\">\n"_L1;
html += "p, li { white-space: pre-wrap; }\n"_L1;
html += "hr { height: 1px; border-width: 0; }\n"_L1;
@@ -2518,7 +2568,9 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
html += u';';
attributesEmitted = true;
}
- } else if (format.hasProperty(QTextFormat::FontPixelSize)) {
+ } else if (format.hasProperty(QTextFormat::FontPixelSize)
+ && format.property(QTextFormat::FontPixelSize)
+ != defaultCharFormat.property(QTextFormat::FontPixelSize)) {
html += " font-size:"_L1;
html += QString::number(format.intProperty(QTextFormat::FontPixelSize));
html += "px;"_L1;
@@ -2597,6 +2649,53 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
html += " -qt-fg-texture-cachekey:"_L1;
html += QString::number(cacheKey);
html += ";"_L1;
+ } else if (brush.style() == Qt::LinearGradientPattern
+ || brush.style() == Qt::RadialGradientPattern
+ || brush.style() == Qt::ConicalGradientPattern) {
+ const QGradient *gradient = brush.gradient();
+ if (gradient->type() == QGradient::LinearGradient) {
+ const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(brush.gradient());
+
+ html += " -qt-foreground: qlineargradient("_L1;
+ html += "x1:"_L1 + QString::number(linearGradient->start().x()) + u',';
+ html += "y1:"_L1 + QString::number(linearGradient->start().y()) + u',';
+ html += "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + u',';
+ html += "y2:"_L1 + QString::number(linearGradient->finalStop().y()) + u',';
+ } else if (gradient->type() == QGradient::RadialGradient) {
+ const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(brush.gradient());
+
+ html += " -qt-foreground: qradialgradient("_L1;
+ html += "cx:"_L1 + QString::number(radialGradient->center().x()) + u',';
+ html += "cy:"_L1 + QString::number(radialGradient->center().y()) + u',';
+ html += "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + u',';
+ html += "fy:"_L1 + QString::number(radialGradient->focalPoint().y()) + u',';
+ html += "radius:"_L1 + QString::number(radialGradient->radius()) + u',';
+ } else {
+ const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(brush.gradient());
+
+ html += " -qt-foreground: qconicalgradient("_L1;
+ html += "cx:"_L1 + QString::number(conicalGradient->center().x()) + u',';
+ html += "cy:"_L1 + QString::number(conicalGradient->center().y()) + u',';
+ html += "angle:"_L1 + QString::number(conicalGradient->angle()) + u',';
+ }
+
+ const QStringList coordinateModes = { "logical"_L1, "stretchtodevice"_L1, "objectbounding"_L1, "object"_L1 };
+ html += "coordinatemode:"_L1;
+ html += coordinateModes.at(int(gradient->coordinateMode()));
+ html += u',';
+
+ const QStringList spreads = { "pad"_L1, "reflect"_L1, "repeat"_L1 };
+ html += "spread:"_L1;
+ html += spreads.at(int(gradient->spread()));
+
+ for (const QGradientStop &stop : gradient->stops()) {
+ html += ",stop:"_L1;
+ html += QString::number(stop.first);
+ html += u' ';
+ html += colorValue(stop.second);
+ }
+
+ html += ");"_L1;
} else {
html += " color:"_L1;
html += colorValue(brush.color());
@@ -2652,6 +2751,63 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
attributesEmitted = true;
}
+ if (format.hasProperty(QTextFormat::TextOutline)) {
+ QPen outlinePen = format.textOutline();
+ html += " -qt-stroke-color:"_L1;
+ html += colorValue(outlinePen.color());
+ html += u';';
+
+ html += " -qt-stroke-width:"_L1;
+ html += QString::number(outlinePen.widthF());
+ html += "px;"_L1;
+
+ html += " -qt-stroke-linecap:"_L1;
+ if (outlinePen.capStyle() == Qt::SquareCap)
+ html += "squarecap;"_L1;
+ else if (outlinePen.capStyle() == Qt::FlatCap)
+ html += "flatcap;"_L1;
+ else if (outlinePen.capStyle() == Qt::RoundCap)
+ html += "roundcap;"_L1;
+
+ html += " -qt-stroke-linejoin:"_L1;
+ if (outlinePen.joinStyle() == Qt::MiterJoin)
+ html += "miterjoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::SvgMiterJoin)
+ html += "svgmiterjoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::BevelJoin)
+ html += "beveljoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::RoundJoin)
+ html += "roundjoin;"_L1;
+
+ if (outlinePen.joinStyle() == Qt::MiterJoin ||
+ outlinePen.joinStyle() == Qt::SvgMiterJoin) {
+ html += " -qt-stroke-miterlimit:"_L1;
+ html += QString::number(outlinePen.miterLimit());
+ html += u';';
+ }
+
+ if (outlinePen.style() == Qt::CustomDashLine && !outlinePen.dashPattern().empty()) {
+ html += " -qt-stroke-dasharray:"_L1;
+ QString dashArrayString;
+ QList<qreal> dashes = outlinePen.dashPattern();
+
+ for (int i = 0; i < dashes.length() - 1; i++) {
+ qreal dash = dashes[i];
+ dashArrayString += QString::number(dash) + u',';
+ }
+
+ dashArrayString += QString::number(dashes.last());
+ html += dashArrayString;
+ html += u';';
+
+ html += " -qt-stroke-dashoffset:"_L1;
+ html += QString::number(outlinePen.dashOffset());
+ html += u';';
+ }
+
+ attributesEmitted = true;
+ }
+
return attributesEmitted;
}
@@ -2836,6 +2992,17 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
html += "<img"_L1;
+ QString maxWidthCss;
+
+ if (imgFmt.hasProperty(QTextFormat::ImageMaxWidth)) {
+ auto length = imgFmt.lengthProperty(QTextFormat::ImageMaxWidth);
+ maxWidthCss += "max-width:"_L1;
+ if (length.type() == QTextLength::PercentageLength)
+ maxWidthCss += QString::number(length.rawValue()) + "%;"_L1;
+ else if (length.type() == QTextLength::FixedLength)
+ maxWidthCss += QString::number(length.rawValue()) + "px;"_L1;
+ }
+
if (imgFmt.hasProperty(QTextFormat::ImageName))
emitAttribute("src", imgFmt.name());
@@ -2852,9 +3019,11 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
emitAttribute("height", QString::number(imgFmt.height()));
if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
- html += " style=\"vertical-align: middle;\""_L1;
+ html += " style=\"vertical-align: middle;"_L1 + maxWidthCss + u'\"';
else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
- html += " style=\"vertical-align: top;\""_L1;
+ html += " style=\"vertical-align: top;"_L1 + maxWidthCss + u'\"';
+ else if (!maxWidthCss.isEmpty())
+ html += " style=\""_L1 + maxWidthCss + u'\"';
if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
emitFloatStyle(imageFrame->frameFormat().position());
@@ -3015,7 +3184,8 @@ void QTextHtmlExporter::emitBlock(const QTextBlock &block)
html += u'"';
}
- QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;");
+ QString styleString;
+ styleString += "margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;"_L1;
if (format.hasProperty(QTextFormat::ListIndent)) {
styleString += " -qt-list-indent: "_L1;
@@ -3445,7 +3615,7 @@ void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType
{
const auto styleAttribute = " style=\""_L1;
html += styleAttribute;
- const int originalHtmlLength = html.size();
+ const qsizetype originalHtmlLength = html.size();
if (frameType == TextFrame)
html += "-qt-table-type: frame;"_L1;
@@ -3554,7 +3724,7 @@ QString QTextDocument::toMarkdown(QTextDocument::MarkdownFeatures features) cons
#if QT_CONFIG(textmarkdownreader)
void QTextDocument::setMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
{
- QTextMarkdownImporter(features).import(this, markdown);
+ QTextMarkdownImporter(this, features).import(markdown);
}
#endif
diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h
index 01ed9789ed..11a8abcb50 100644
--- a/src/gui/text/qtextdocument.h
+++ b/src/gui/text/qtextdocument.h
@@ -35,22 +35,21 @@ class QTextCursor;
namespace Qt
{
+#if QT_GUI_REMOVED_SINCE(6, 7)
Q_GUI_EXPORT bool mightBeRichText(const QString&);
+#endif
+ Q_GUI_EXPORT bool mightBeRichText(QAnyStringView);
Q_GUI_EXPORT QString convertFromPlainText(const QString &plain, WhiteSpaceMode mode = WhiteSpacePre);
}
class Q_GUI_EXPORT QAbstractUndoItem
{
public:
- virtual ~QAbstractUndoItem() = 0;
+ virtual ~QAbstractUndoItem();
virtual void undo() = 0;
virtual void redo() = 0;
};
-inline QAbstractUndoItem::~QAbstractUndoItem()
-{
-}
-
class QTextDocumentPrivate;
class Q_GUI_EXPORT QTextDocument : public QObject
@@ -102,7 +101,8 @@ public:
enum MetaInformation {
DocumentTitle,
DocumentUrl,
- CssMedia
+ CssMedia,
+ FrontMatter,
};
void setMetaInformation(MetaInformation info, const QString &);
QString metaInformation(MetaInformation info) const;
@@ -116,7 +116,7 @@ public:
enum MarkdownFeature {
MarkdownNoHTML = 0x0020 | 0x0040,
MarkdownDialectCommonMark = 0,
- MarkdownDialectGitHub = 0x0004 | 0x0008 | 0x0400 | 0x0100 | 0x0200 | 0x0800 | 0x4000
+ MarkdownDialectGitHub = 0x0004 | 0x0008 | 0x0400 | 0x0100 | 0x0200 | 0x0800 | 0x4000 | 0x100000
};
Q_DECLARE_FLAGS(MarkdownFeatures, MarkdownFeature)
Q_FLAG(MarkdownFeatures)
diff --git a/src/gui/text/qtextdocument_p.cpp b/src/gui/text/qtextdocument_p.cpp
index 9e630f3787..3c1fc04d4b 100644
--- a/src/gui/text/qtextdocument_p.cpp
+++ b/src/gui/text/qtextdocument_p.cpp
@@ -349,8 +349,10 @@ int QTextDocumentPrivate::insert_block(int pos, uint strPos, int format, int blo
QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(blockFormat));
if (group) {
group->blockInserted(QTextBlock(this, b));
- docChangeOldLength--;
- docChangeLength--;
+ if (command != QTextUndoCommand::BlockDeleted) {
+ docChangeOldLength--;
+ docChangeLength--;
+ }
}
QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(formats.format(format)));
@@ -443,7 +445,7 @@ void QTextDocumentPrivate::insert(int pos, int strPos, int strLength, int format
finishEdit();
}
-void QTextDocumentPrivate::insert(int pos, const QString &str, int format)
+void QTextDocumentPrivate::insert(int pos, QStringView str, int format)
{
if (str.size() == 0)
return;
diff --git a/src/gui/text/qtextdocument_p.h b/src/gui/text/qtextdocument_p.h
index 84a79691e9..1c4edc4329 100644
--- a/src/gui/text/qtextdocument_p.h
+++ b/src/gui/text/qtextdocument_p.h
@@ -142,7 +142,9 @@ public:
void setLayout(QAbstractTextDocumentLayout *layout);
- void insert(int pos, const QString &text, int format);
+ void insert(int pos, QStringView text, int format);
+ void insert(int pos, QChar c, int format)
+ { insert(pos, QStringView(&c, 1), format); }
void insert(int pos, int strPos, int strLength, int format);
int insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation = QTextUndoCommand::MoveCursor);
int insertBlock(QChar blockSeparator, int pos, int blockFormat, int charFormat,
@@ -354,6 +356,7 @@ public:
QString title;
QString url;
QString cssMedia;
+ QString frontMatter;
qreal indentWidth;
qreal documentMargin;
QUrl baseUrl;
diff --git a/src/gui/text/qtextdocumentfragment.cpp b/src/gui/text/qtextdocumentfragment.cpp
index 9e99992929..1b6e76c201 100644
--- a/src/gui/text/qtextdocumentfragment.cpp
+++ b/src/gui/text/qtextdocumentfragment.cpp
@@ -488,7 +488,8 @@ void QTextHtmlImporter::import()
* means there was a tag closing in the input html
*/
if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
- blockTagClosed = closeTag();
+ const bool lastBlockTagClosed = closeTag();
+ blockTagClosed = blockTagClosed || lastBlockTagClosed;
// visually collapse subsequent block tags, but if the element after the closed block tag
// is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
// hasBlock to false.
@@ -540,6 +541,7 @@ void QTextHtmlImporter::import()
appendBlock(block, currentNode->charFormat);
+ blockTagClosed = false;
hasBlock = true;
}
@@ -575,14 +577,12 @@ bool QTextHtmlImporter::appendNodeText()
if (wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
compressNextWhitespace = PreserveWhiteSpace;
- QString text = currentNode->text;
+ const QString text = currentNode->text;
QString textToInsert;
textToInsert.reserve(text.size());
- for (int i = 0; i < text.size(); ++i) {
- QChar ch = text.at(i);
-
+ for (QChar ch : text) {
if (ch.isSpace()
&& ch != QChar::Nbsp
&& ch != QChar::ParagraphSeparator) {
@@ -1313,8 +1313,7 @@ QTextDocumentFragment QTextDocumentFragment::fromMarkdown(const QString &markdow
QTextDocumentFragment res;
res.d = new QTextDocumentFragmentPrivate;
- QTextMarkdownImporter importer(features);
- importer.import(res.d->doc, markdown);
+ QTextMarkdownImporter(res.d->doc, features).import(markdown);
return res;
}
diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp
index fe049766ab..452f814231 100644
--- a/src/gui/text/qtextdocumentlayout.cpp
+++ b/src/gui/text/qtextdocumentlayout.cpp
@@ -25,6 +25,7 @@
#include <qbasictimer.h>
#include "private/qfunctions_p.h"
#include <qloggingcategory.h>
+#include <QtCore/qpointer.h>
#include <algorithm>
@@ -1817,11 +1818,20 @@ void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter
if (r >= headerRowCount)
topMargin += td->headerHeight.toReal();
- if (!td->borderCollapse && td->border != 0) {
+ // If cell border configured, don't draw default border for cells. It will be taken care later by
+ // drawTableCellBorder().
+ bool cellBorderConfigured = (cell.format().hasProperty(QTextFormat::TableCellLeftBorder) ||
+ cell.format().hasProperty(QTextFormat::TableCellTopBorder) ||
+ cell.format().hasProperty(QTextFormat::TableCellRightBorder) ||
+ cell.format().hasProperty(QTextFormat::TableCellBottomBorder));
+
+ if (!td->borderCollapse && td->border != 0 && !cellBorderConfigured) {
const QBrush oldBrush = painter->brush();
const QPen oldPen = painter->pen();
- const qreal border = td->border.toReal();
+ // If border is configured for the table (and not explicitly for the cell), then
+ // always draw 1px border around the cell
+ const qreal border = 1;
QRectF borderRect(cellRect.left() - border, cellRect.top() - border, cellRect.width() + border, cellRect.height() + border);
@@ -1884,7 +1894,8 @@ void QTextDocumentLayoutPrivate::drawTableCell(const QRectF &cellRect, QPainter
}
// paint over the background - otherwise we would have to adjust the background paint cellRect for the border values
- drawTableCellBorder(cellRect, painter, table, td, cell);
+ if (cellBorderConfigured)
+ drawTableCellBorder(cellRect, painter, table, td, cell);
const QFixed verticalOffset = td->cellVerticalOffsets.at(c + r * table->columns());
@@ -2205,17 +2216,15 @@ void QTextDocumentLayoutPrivate::drawListItem(const QPointF &offset, QPainter *p
}
case QTextListFormat::ListSquare:
if (!marker)
- painter->fillRect(r, brush);
+ painter->fillRect(r, painter->pen().brush());
break;
case QTextListFormat::ListCircle:
- if (!marker) {
- painter->setPen(QPen(brush, 0));
+ if (!marker)
painter->drawEllipse(r.translated(0.5, 0.5)); // pixel align for sharper rendering
- }
break;
case QTextListFormat::ListDisc:
if (!marker) {
- painter->setBrush(brush);
+ painter->setBrush(painter->pen().brush());
painter->setPen(Qt::NoPen);
painter->drawEllipse(r);
}
@@ -3111,7 +3120,7 @@ void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayout
QTextBlockFormat previousBlockFormat = previousIt.currentBlock().blockFormat();
QFixed maximumBlockWidth = 0;
- while (!it.atEnd()) {
+ while (!it.atEnd() && layoutStruct->absoluteY() < QFIXED_MAX) {
QTextFrame *c = it.currentFrame();
int docPos;
@@ -3361,7 +3370,7 @@ void QTextDocumentLayoutPrivate::layoutFlow(QTextFrame::Iterator it, QTextLayout
if (!fd->floats.isEmpty())
contentHasAlignment = true;
- if (it.atEnd()) {
+ if (it.atEnd() || layoutStruct->absoluteY() >= QFIXED_MAX) {
//qDebug("layout done!");
currentLazyLayoutPosition = -1;
QCheckPoint cp;
@@ -3547,6 +3556,11 @@ void QTextDocumentLayoutPrivate::layoutBlock(const QTextBlock &bl, int blockPosi
while (layoutStruct->pageHeight > 0 && layoutStruct->absoluteY() + lineBreakHeight > layoutStruct->pageBottom &&
layoutStruct->contentHeight() >= lineBreakHeight) {
+ if (layoutStruct->pageHeight == QFIXED_MAX) {
+ layoutStruct->y = QFIXED_MAX - layoutStruct->frameY;
+ break;
+ }
+
layoutStruct->newPage();
floatMargins(layoutStruct->y, layoutStruct, &left, &right);
diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp
index 0b3e651ff8..08512bead5 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.cpp
@@ -36,15 +36,10 @@ public:
Itemizer(const QString &string, const QScriptAnalysis *analysis, QScriptItemArray &items)
: m_string(string),
m_analysis(analysis),
- m_items(items),
- m_splitter(nullptr)
+ m_items(items)
{
}
- ~Itemizer()
- {
- delete m_splitter;
- }
-
+ ~Itemizer() = default;
/// generate the script items
/// The caps parameter is used to choose the algorithm of splitting text and assigning roles to the textitems
void generate(int start, int length, QFont::Capitalization caps)
@@ -101,7 +96,7 @@ private:
return;
if (!m_splitter)
- m_splitter = new QTextBoundaryFinder(QTextBoundaryFinder::Word,
+ m_splitter = std::make_unique<QTextBoundaryFinder>(QTextBoundaryFinder::Word,
m_string.constData(), m_string.size(),
/*buffer*/nullptr, /*buffer size*/0);
@@ -171,7 +166,7 @@ private:
const QString &m_string;
const QScriptAnalysis * const m_analysis;
QScriptItemArray &m_items;
- QTextBoundaryFinder *m_splitter;
+ std::unique_ptr<QTextBoundaryFinder> m_splitter;
};
// -----------------------------------------------------------------------------------------------------
@@ -1401,17 +1396,19 @@ void QTextEngine::shapeText(int item) const
QFontEngine *fontEngine = this->fontEngine(si, &si.ascent, &si.descent, &si.leading);
+#if QT_CONFIG(harfbuzz)
bool kerningEnabled;
+#endif
bool letterSpacingIsAbsolute;
bool shapingEnabled = false;
- QHash<quint32, quint32> features;
+ QHash<QFont::Tag, quint32> features;
QFixed letterSpacing, wordSpacing;
#ifndef QT_NO_RAWFONT
if (useRawFont) {
QTextCharFormat f = format(&si);
QFont font = f.font();
- kerningEnabled = font.kerning();
# if QT_CONFIG(harfbuzz)
+ kerningEnabled = font.kerning();
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.styleStrategy() & QFont::PreferNoShaping) == 0;
# endif
@@ -1423,8 +1420,8 @@ void QTextEngine::shapeText(int item) const
#endif
{
QFont font = this->font(si);
- kerningEnabled = font.d->kerning;
#if QT_CONFIG(harfbuzz)
+ kerningEnabled = font.d->kerning;
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0;
#endif
@@ -1439,8 +1436,7 @@ void QTextEngine::shapeText(int item) const
// split up the item into parts that come from different font engines
// k * 3 entries, array[k] == index in string, array[k + 1] == index in glyphs, array[k + 2] == engine index
- QList<uint> itemBoundaries;
- itemBoundaries.reserve(24);
+ QVarLengthArray<uint, 24> itemBoundaries;
QGlyphLayout initialGlyphs = availableGlyphs(&si);
int nGlyphs = initialGlyphs.numGlyphs;
@@ -1451,7 +1447,7 @@ void QTextEngine::shapeText(int item) const
shapingEnabled
? QFontEngine::GlyphIndicesOnly
: QFontEngine::ShaperFlag(0);
- if (!fontEngine->stringToCMap(reinterpret_cast<const QChar *>(string), itemLength, &initialGlyphs, &nGlyphs, shaperFlags))
+ if (fontEngine->stringToCMap(reinterpret_cast<const QChar *>(string), itemLength, &initialGlyphs, &nGlyphs, shaperFlags) < 0)
Q_UNREACHABLE();
}
@@ -1460,9 +1456,9 @@ void QTextEngine::shapeText(int item) const
for (int i = 0, glyph_pos = 0; i < itemLength; ++i, ++glyph_pos) {
const uint engineIdx = initialGlyphs.glyphs[glyph_pos] >> 24;
if (lastEngine != engineIdx) {
- itemBoundaries.append(i);
- itemBoundaries.append(glyph_pos);
- itemBoundaries.append(engineIdx);
+ itemBoundaries.push_back(i);
+ itemBoundaries.push_back(glyph_pos);
+ itemBoundaries.push_back(engineIdx);
if (engineIdx != 0) {
QFontEngine *actualFontEngine = static_cast<QFontEngineMulti *>(fontEngine)->engine(engineIdx);
@@ -1478,9 +1474,9 @@ void QTextEngine::shapeText(int item) const
++i;
}
} else {
- itemBoundaries.append(0);
- itemBoundaries.append(0);
- itemBoundaries.append(0);
+ itemBoundaries.push_back(0);
+ itemBoundaries.push_back(0);
+ itemBoundaries.push_back(0);
}
#if QT_CONFIG(harfbuzz)
@@ -1602,10 +1598,10 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si,
const ushort *string,
int itemLength,
QFontEngine *fontEngine,
- const QList<uint> &itemBoundaries,
+ QSpan<uint> itemBoundaries,
bool kerningEnabled,
bool hasLetterSpacing,
- const QHash<quint32, quint32> &fontFeatures) const
+ const QHash<QFont::Tag, quint32> &fontFeatures) const
{
uint glyphs_shaped = 0;
@@ -1624,7 +1620,7 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si,
// ### TODO get_default_for_script?
props.language = hb_language_get_default(); // use default language from locale
- for (int k = 0; k < itemBoundaries.size(); k += 3) {
+ for (qsizetype k = 0; k < itemBoundaries.size(); k += 3) {
const uint item_pos = itemBoundaries[k];
const uint item_length = (k + 4 < itemBoundaries.size() ? itemBoundaries[k + 3] : itemLength) - item_pos;
const uint engineIdx = itemBoundaries[k + 2];
@@ -1660,19 +1656,19 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si,
bool dontLigate = hasLetterSpacing && !scriptRequiresOpenType;
- QHash<quint32, quint32> features;
- features.insert(HB_TAG('k','e','r','n'), !!kerningEnabled);
+ QHash<QFont::Tag, quint32> features;
+ features.insert(QFont::Tag("kern"), !!kerningEnabled);
if (dontLigate) {
- features.insert(HB_TAG('l','i','g','a'), false);
- features.insert(HB_TAG('c','l','i','g'), false);
- features.insert(HB_TAG('d','l','i','g'), false);
- features.insert(HB_TAG('h','l','i','g'), false);
+ features.insert(QFont::Tag("liga"), false);
+ features.insert(QFont::Tag("clig"), false);
+ features.insert(QFont::Tag("dlig"), false);
+ features.insert(QFont::Tag("hlig"), false);
}
features.insert(fontFeatures);
QVarLengthArray<hb_feature_t, 16> featureArray;
for (auto it = features.constBegin(); it != features.constEnd(); ++it) {
- featureArray.append({ it.key(),
+ featureArray.append({ it.key().value(),
it.value(),
HB_FEATURE_GLOBAL_START,
HB_FEATURE_GLOBAL_END });
@@ -2659,14 +2655,15 @@ QTextEngine::LayoutData::LayoutData()
currentMaxWidth = 0;
}
-QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, int _allocated)
+QTextEngine::LayoutData::LayoutData(const QString &str, void **stack_memory, qsizetype _allocated)
: string(str)
{
allocated = _allocated;
- int space_charAttributes = int(sizeof(QCharAttributes) * string.size() / sizeof(void*) + 1);
- int space_logClusters = int(sizeof(unsigned short) * string.size() / sizeof(void*) + 1);
- available_glyphs = ((int)allocated - space_charAttributes - space_logClusters)*(int)sizeof(void*)/(int)QGlyphLayout::SpaceNeeded;
+ constexpr qsizetype voidSize = sizeof(void*);
+ qsizetype space_charAttributes = sizeof(QCharAttributes) * string.size() / voidSize + 1;
+ qsizetype space_logClusters = sizeof(unsigned short) * string.size() / voidSize + 1;
+ available_glyphs = (allocated - space_charAttributes - space_logClusters) * voidSize / QGlyphLayout::SpaceNeeded;
if (available_glyphs < str.size()) {
// need to allocate on the heap
@@ -2707,15 +2704,16 @@ bool QTextEngine::LayoutData::reallocate(int totalGlyphs)
return true;
}
- int space_charAttributes = int(sizeof(QCharAttributes) * string.size() / sizeof(void*) + 1);
- int space_logClusters = int(sizeof(unsigned short) * string.size() / sizeof(void*) + 1);
- int space_glyphs = (totalGlyphs * QGlyphLayout::SpaceNeeded) / sizeof(void *) + 2;
+ const qsizetype space_charAttributes = (sizeof(QCharAttributes) * string.size() / sizeof(void*) + 1);
+ const qsizetype space_logClusters = (sizeof(unsigned short) * string.size() / sizeof(void*) + 1);
+ const qsizetype space_glyphs = qsizetype(totalGlyphs) * QGlyphLayout::SpaceNeeded / sizeof(void *) + 2;
- int newAllocated = space_charAttributes + space_glyphs + space_logClusters;
- // These values can be negative if the length of string/glyphs causes overflow,
+ const qsizetype newAllocated = space_charAttributes + space_glyphs + space_logClusters;
+ // Check if the length of string/glyphs causes int overflow,
// we can't layout such a long string all at once, so return false here to
// indicate there is a failure
- if (space_charAttributes < 0 || space_logClusters < 0 || space_glyphs < 0 || newAllocated < allocated) {
+ if (size_t(space_charAttributes) > INT_MAX || size_t(space_logClusters) > INT_MAX || totalGlyphs < 0
+ || size_t(space_glyphs) > INT_MAX || size_t(newAllocated) > INT_MAX || newAllocated < allocated) {
layoutState = LayoutFailed;
return false;
}
@@ -2735,7 +2733,7 @@ bool QTextEngine::LayoutData::reallocate(int totalGlyphs)
logClustersPtr = (unsigned short *) m;
m += space_logClusters;
- const int space_preGlyphLayout = space_charAttributes + space_logClusters;
+ const qsizetype space_preGlyphLayout = space_charAttributes + space_logClusters;
if (allocated < space_preGlyphLayout)
memset(memory + allocated, 0, (space_preGlyphLayout - allocated)*sizeof(void *));
@@ -2745,6 +2743,21 @@ bool QTextEngine::LayoutData::reallocate(int totalGlyphs)
return true;
}
+void QGlyphLayout::copy(QGlyphLayout *oldLayout)
+{
+ Q_ASSERT(offsets != oldLayout->offsets);
+
+ int n = std::min(numGlyphs, oldLayout->numGlyphs);
+
+ memcpy(offsets, oldLayout->offsets, n * sizeof(QFixedPoint));
+ memcpy(attributes, oldLayout->attributes, n * sizeof(QGlyphAttributes));
+ memcpy(justifications, oldLayout->justifications, n * sizeof(QGlyphJustification));
+ memcpy(advances, oldLayout->advances, n * sizeof(QFixed));
+ memcpy(glyphs, oldLayout->glyphs, n * sizeof(glyph_t));
+
+ numGlyphs = n;
+}
+
// grow to the new size, copying the existing data to the new layout
void QGlyphLayout::grow(char *address, int totalGlyphs)
{
@@ -2955,11 +2968,11 @@ static inline bool prevCharJoins(const QString &string, int pos)
return joining == QChar::Joining_Dual || joining == QChar::Joining_Causing;
}
-static inline bool isRetainableControlCode(QChar c)
+static constexpr bool isRetainableControlCode(char16_t c) noexcept
{
- return (c.unicode() >= 0x202a && c.unicode() <= 0x202e) // LRE, RLE, PDF, LRO, RLO
- || (c.unicode() >= 0x200e && c.unicode() <= 0x200f) // LRM, RLM
- || (c.unicode() >= 0x2066 && c.unicode() <= 0x2069); // LRI, RLI, FSI, PDI
+ return (c >= 0x202a && c <= 0x202e) // LRE, RLE, PDF, LRO, RLO
+ || (c >= 0x200e && c <= 0x200f) // LRM, RLM
+ || (c >= 0x2066 && c <= 0x2069); // LRI, RLI, FSI, PDI
}
static QString stringMidRetainingBidiCC(const QString &string,
@@ -2972,14 +2985,14 @@ static QString stringMidRetainingBidiCC(const QString &string,
{
QString prefix;
for (int i=subStringFrom; i<midStart; ++i) {
- QChar c = string.at(i);
+ char16_t c = string.at(i).unicode();
if (isRetainableControlCode(c))
prefix += c;
}
QString suffix;
for (int i=midStart + midLength; i<subStringTo; ++i) {
- QChar c = string.at(i);
+ char16_t c = string.at(i).unicode();
if (isRetainableControlCode(c))
suffix += c;
}
@@ -3036,7 +3049,7 @@ QString QTextEngine::elidedText(Qt::TextElideMode mode, QFixed width, int flags,
{
QFontEngine *engine = fnt.d->engineForScript(QChar::Script_Common);
- QChar ellipsisChar = u'\x2026';
+ constexpr char16_t ellipsisChar = u'\x2026';
// We only want to use the ellipsis character if it is from the main
// font (not one of the fallbacks), since using a fallback font
@@ -3048,7 +3061,7 @@ QString QTextEngine::elidedText(Qt::TextElideMode mode, QFixed width, int flags,
engine = multiEngine->engine(0);
}
- glyph_t glyph = engine->glyphIndex(ellipsisChar.unicode());
+ glyph_t glyph = engine->glyphIndex(ellipsisChar);
QGlyphLayout glyphs;
glyphs.numGlyphs = 1;
@@ -3068,7 +3081,7 @@ QString QTextEngine::elidedText(Qt::TextElideMode mode, QFixed width, int flags,
ellipsisText = QStringLiteral("...");
} else {
engine = fnt.d->engineForScript(QChar::Script_Common);
- glyph = engine->glyphIndex(ellipsisChar.unicode());
+ glyph = engine->glyphIndex(ellipsisChar);
engine->recalcAdvances(&glyphs, { });
ellipsisText = ellipsisChar;
}
@@ -3185,7 +3198,7 @@ void QTextEngine::setBoundary(int strPos) const
QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const
{
- const QScriptItem &si = layoutData->items[item];
+ const QScriptItem &si = layoutData->items.at(item);
QFixed dpiScale = 1;
if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
@@ -3227,7 +3240,7 @@ QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const
// find next tab to calculate the width required.
tab = QFixed::fromReal(tabSpec.position);
for (int i=item + 1; i < layoutData->items.size(); i++) {
- const QScriptItem &item = layoutData->items[i];
+ const QScriptItem &item = layoutData->items.at(i);
if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it.
tabSectionEnd = item.position;
break;
diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h
index 6cacd6b556..c01d3a0711 100644
--- a/src/gui/text/qtextengine_p.h
+++ b/src/gui/text/qtextengine_p.h
@@ -26,6 +26,7 @@
#include "QtCore/qlist.h"
#include "QtCore/qnamespace.h"
#include "QtCore/qset.h"
+#include <QtCore/qspan.h>
#include "QtCore/qstring.h"
#include "QtCore/qvarlengtharray.h"
@@ -158,10 +159,8 @@ Q_DECLARE_TYPEINFO(QGlyphAttributes, Q_PRIMITIVE_TYPE);
struct QGlyphLayout
{
- enum {
- SpaceNeeded = sizeof(glyph_t) + sizeof(QFixed) + sizeof(QFixedPoint)
- + sizeof(QGlyphAttributes) + sizeof(QGlyphJustification)
- };
+ static constexpr qsizetype SpaceNeeded = sizeof(glyph_t) + sizeof(QFixed) + sizeof(QFixedPoint)
+ + sizeof(QGlyphAttributes) + sizeof(QGlyphJustification);
// init to 0 not needed, done when shaping
QFixedPoint *offsets; // 8 bytes per element
@@ -177,7 +176,7 @@ struct QGlyphLayout
inline explicit QGlyphLayout(char *address, int totalGlyphs)
{
offsets = reinterpret_cast<QFixedPoint *>(address);
- int offset = totalGlyphs * sizeof(QFixedPoint);
+ qsizetype offset = totalGlyphs * sizeof(QFixedPoint);
glyphs = reinterpret_cast<glyph_t *>(address + offset);
offset += totalGlyphs * sizeof(glyph_t);
advances = reinterpret_cast<QFixed *>(address + offset);
@@ -210,7 +209,7 @@ struct QGlyphLayout
last = numGlyphs;
if (first == 0 && last == numGlyphs
&& reinterpret_cast<char *>(offsets + numGlyphs) == reinterpret_cast<char *>(glyphs)) {
- memset(static_cast<void *>(offsets), 0, (numGlyphs * SpaceNeeded));
+ memset(static_cast<void *>(offsets), 0, qsizetype(numGlyphs) * SpaceNeeded);
} else {
const int num = last - first;
memset(static_cast<void *>(offsets + first), 0, num * sizeof(QFixedPoint));
@@ -225,6 +224,7 @@ struct QGlyphLayout
return reinterpret_cast<char *>(offsets);
}
+ void copy(QGlyphLayout *other);
void grow(char *address, int totalGlyphs);
};
@@ -371,12 +371,12 @@ public:
LayoutFailed
};
struct Q_GUI_EXPORT LayoutData {
- LayoutData(const QString &str, void **stack_memory, int mem_size);
+ LayoutData(const QString &str, void **stack_memory, qsizetype mem_size);
LayoutData();
~LayoutData();
mutable QScriptItemArray items;
- int allocated;
- int available_glyphs;
+ qsizetype allocated;
+ qsizetype available_glyphs;
void **memory;
unsigned short *logClustersPtr;
QGlyphLayout glyphLayout;
@@ -625,10 +625,10 @@ private:
const ushort *string,
int itemLength,
QFontEngine *fontEngine,
- const QList<uint> &itemBoundaries,
+ QSpan<uint> itemBoundaries,
bool kerningEnabled,
bool hasLetterSpacing,
- const QHash<quint32, quint32> &features) const;
+ const QHash<QFont::Tag, quint32> &features) const;
#endif
int endOfLine(int lineNum);
diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp
index ec91236f55..dacef70812 100644
--- a/src/gui/text/qtextformat.cpp
+++ b/src/gui/text/qtextformat.cpp
@@ -405,27 +405,27 @@ Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QTextFormat &fmt
{
QMap<int, QVariant> properties = fmt.properties();
if (stream.version() < QDataStream::Qt_6_0) {
- auto it = properties.find(QTextFormat::FontLetterSpacingType);
- if (it != properties.end()) {
+ auto it = properties.constFind(QTextFormat::FontLetterSpacingType);
+ if (it != properties.cend()) {
properties[QTextFormat::OldFontLetterSpacingType] = it.value();
properties.erase(it);
}
- it = properties.find(QTextFormat::FontStretch);
- if (it != properties.end()) {
+ it = properties.constFind(QTextFormat::FontStretch);
+ if (it != properties.cend()) {
properties[QTextFormat::OldFontStretch] = it.value();
properties.erase(it);
}
- it = properties.find(QTextFormat::TextUnderlineColor);
- if (it != properties.end()) {
+ it = properties.constFind(QTextFormat::TextUnderlineColor);
+ if (it != properties.cend()) {
properties[QTextFormat::OldTextUnderlineColor] = it.value();
properties.erase(it);
}
- it = properties.find(QTextFormat::FontFamilies);
- if (it != properties.end()) {
- properties[QTextFormat::OldFontFamily] = QVariant(it.value().toStringList().first());
+ it = properties.constFind(QTextFormat::FontFamilies);
+ if (it != properties.cend()) {
+ properties[QTextFormat::OldFontFamily] = QVariant(it.value().toStringList().constFirst());
properties.erase(it);
}
}
@@ -745,6 +745,7 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextTableCellFormat &
\value ImageWidth
\value ImageHeight
\value ImageQuality
+ \value ImageMaxWidth This enum value has been added in Qt 6.8.
Selection properties
@@ -955,7 +956,11 @@ void QTextFormat::merge(const QTextFormat &other)
p->props.reserve(p->props.size() + otherProps.size());
for (int i = 0; i < otherProps.size(); ++i) {
const QT_PREPEND_NAMESPACE(Property) &prop = otherProps.at(i);
- p->insertProperty(prop.key, prop.value);
+ if (prop.value.isValid()) {
+ p->insertProperty(prop.key, prop.value);
+ } else {
+ p->clearProperty(prop.key);
+ }
}
}
@@ -1212,10 +1217,8 @@ void QTextFormat::setProperty(int propertyId, const QVariant &value)
{
if (!d)
d = new QTextFormatPrivate;
- if (!value.isValid())
- clearProperty(propertyId);
- else
- d->insertProperty(propertyId, value);
+
+ d->insertProperty(propertyId, value);
}
/*!
@@ -2169,7 +2172,7 @@ QFont QTextCharFormat::font() const
associated QTextBlockFormat that specifies its characteristics.
To cater for left-to-right and right-to-left languages you can set
- a block's direction with setDirection(). Paragraph alignment is
+ a block's direction with setLayoutDirection(). Paragraph alignment is
set with setAlignment(). Margins are controlled by setTopMargin(),
setBottomMargin(), setLeftMargin(), setRightMargin(). Overall
indentation is set with setIndent(), the indentation of the first
@@ -2240,13 +2243,8 @@ void QTextBlockFormat::setTabPositions(const QList<QTextOption::Tab> &tabs)
{
QList<QVariant> list;
list.reserve(tabs.size());
- QList<QTextOption::Tab>::ConstIterator iter = tabs.constBegin();
- while (iter != tabs.constEnd()) {
- QVariant v;
- v.setValue(*iter);
- list.append(v);
- ++iter;
- }
+ for (const auto &e : tabs)
+ list.append(QVariant::fromValue(e));
setProperty(TabPositions, list);
}
@@ -2262,13 +2260,10 @@ QList<QTextOption::Tab> QTextBlockFormat::tabPositions() const
if (variant.isNull())
return QList<QTextOption::Tab>();
QList<QTextOption::Tab> answer;
- QList<QVariant> variantsList = qvariant_cast<QList<QVariant> >(variant);
- QList<QVariant>::Iterator iter = variantsList.begin();
+ const QList<QVariant> variantsList = qvariant_cast<QList<QVariant> >(variant);
answer.reserve(variantsList.size());
- while(iter != variantsList.end()) {
- answer.append( qvariant_cast<QTextOption::Tab>(*iter));
- ++iter;
- }
+ for (const auto &e: variantsList)
+ answer.append(qvariant_cast<QTextOption::Tab>(e));
return answer;
}
@@ -3162,7 +3157,8 @@ QTextTableFormat::QTextTableFormat()
: QTextFrameFormat()
{
setObjectType(TableObject);
- setCellSpacing(2);
+ setCellPadding(4);
+ setBorderCollapse(true);
setBorder(1);
}
@@ -3431,7 +3427,7 @@ QTextImageFormat::QTextImageFormat(const QTextFormat &fmt)
Sets the \a width of the rectangle occupied by the image.
- \sa width(), setHeight()
+ \sa width(), setHeight(), maximumWidth()
*/
@@ -3443,6 +3439,24 @@ QTextImageFormat::QTextImageFormat(const QTextFormat &fmt)
\sa height(), setWidth()
*/
+/*!
+ \fn void QTextImageFormat::setMaximumWidth(QTextLength maximumWidth)
+
+ Sets the \a maximumWidth of the rectangle occupied by the image. This
+ can be an absolute number or a percentage of the available document size.
+
+ \sa width(), setHeight()
+*/
+
+
+/*!
+ \fn QTextLength QTextImageFormat::maximumWidth() const
+
+ Returns the maximum width of the rectangle occupied by the image.
+
+ \sa width(), setMaximumWidth()
+*/
+
/*!
\fn void QTextImageFormat::setHeight(qreal height)
@@ -3999,7 +4013,7 @@ bool QTextFormatCollection::hasFormatCached(const QTextFormat &format) const
int QTextFormatCollection::objectFormatIndex(int objectIndex) const
{
- if (objectIndex == -1)
+ if (objectIndex == -1 || objectIndex >= objFormats.size())
return -1;
return objFormats.at(objectIndex);
}
diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h
index c009d328cb..2fa86ed0d1 100644
--- a/src/gui/text/qtextformat.h
+++ b/src/gui/text/qtextformat.h
@@ -241,6 +241,7 @@ public:
ImageWidth = 0x5010,
ImageHeight = 0x5011,
ImageQuality = 0x5014,
+ ImageMaxWidth = 0x5015,
// internal
/*
@@ -796,6 +797,10 @@ public:
inline qreal width() const
{ return doubleProperty(ImageWidth); }
+ inline void setMaximumWidth(QTextLength maxWidth);
+ inline QTextLength maximumWidth() const
+ { return lengthProperty(ImageMaxWidth); }
+
inline void setHeight(qreal height);
inline qreal height() const
{ return doubleProperty(ImageHeight); }
@@ -823,6 +828,9 @@ inline void QTextImageFormat::setName(const QString &aname)
inline void QTextImageFormat::setWidth(qreal awidth)
{ setProperty(ImageWidth, awidth); }
+inline void QTextImageFormat::setMaximumWidth(QTextLength maxWidth)
+{ setProperty(ImageMaxWidth, maxWidth); }
+
inline void QTextImageFormat::setHeight(qreal aheight)
{ setProperty(ImageHeight, aheight); }
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
index 16e2d7bd3a..54c291b82e 100644
--- a/src/gui/text/qtexthtmlparser.cpp
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -413,17 +413,17 @@ static const QTextHtmlElement elements[Html_NumElements]= {
{ "var", Html_var, QTextHtmlElement::DisplayInline },
};
-static bool operator<(const QString &str, const QTextHtmlElement &e)
+static bool operator<(QStringView str, const QTextHtmlElement &e)
{
return str < QLatin1StringView(e.name);
}
-static bool operator<(const QTextHtmlElement &e, const QString &str)
+static bool operator<(const QTextHtmlElement &e, QStringView str)
{
return QLatin1StringView(e.name) < str;
}
-static const QTextHtmlElement *lookupElementHelper(const QString &element)
+static const QTextHtmlElement *lookupElementHelper(QStringView element)
{
const QTextHtmlElement *start = &elements[0];
const QTextHtmlElement *end = &elements[Html_NumElements];
@@ -433,7 +433,7 @@ static const QTextHtmlElement *lookupElementHelper(const QString &element)
return e;
}
-int QTextHtmlParser::lookupElement(const QString &element)
+int QTextHtmlParser::lookupElement(QStringView element)
{
const QTextHtmlElement *e = lookupElementHelper(element);
if (!e)
@@ -1181,7 +1181,7 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
QCss::ValueExtractor extractor(declarations);
extractor.extractBox(margin, padding);
- if (id == Html_td || id == Html_th) {
+ auto getBorderValues = [&extractor](qreal *borderWidth, QBrush *borderBrush, QTextFrameFormat::BorderStyle *borderStyles) {
QCss::BorderStyle cssStyles[4];
int cssBorder[4];
QSize cssRadii[4]; // unused
@@ -1193,20 +1193,24 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
// QCss::BorderWidth parsing below which expects a single value
// will not work as expected - which in this case does not matter
// because tableBorder is not relevant for cells.
- extractor.extractBorder(cssBorder, tableCellBorderBrush, cssStyles, cssRadii);
+ bool hit = extractor.extractBorder(cssBorder, borderBrush, cssStyles, cssRadii);
for (int i = 0; i < 4; ++i) {
- tableCellBorderStyle[i] = toQTextFrameFormat(cssStyles[i]);
- tableCellBorder[i] = static_cast<qreal>(cssBorder[i]);
+ borderStyles[i] = toQTextFrameFormat(cssStyles[i]);
+ borderWidth[i] = static_cast<qreal>(cssBorder[i]);
}
- }
+ return hit;
+ };
+
+ if (id == Html_td || id == Html_th)
+ getBorderValues(tableCellBorder, tableCellBorderBrush, tableCellBorderStyle);
for (int i = 0; i < declarations.size(); ++i) {
const QCss::Declaration &decl = declarations.at(i);
if (decl.d->values.isEmpty()) continue;
QCss::KnownValue identifier = QCss::UnknownValue;
- if (decl.d->values.first().type == QCss::Value::KnownIdentifier)
- identifier = static_cast<QCss::KnownValue>(decl.d->values.first().variant.toInt());
+ if (decl.d->values.constFirst().type == QCss::Value::KnownIdentifier)
+ identifier = static_cast<QCss::KnownValue>(decl.d->values.constFirst().variant.toInt());
switch (decl.d->propertyId) {
case QCss::BorderColor: borderBrush = QBrush(decl.colorValue()); break;
@@ -1220,6 +1224,19 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
tableBorder = borders[0];
}
break;
+ case QCss::Border: {
+ qreal tblBorder[4];
+ QBrush tblBorderBrush[4];
+ QTextFrameFormat::BorderStyle tblBorderStyle[4];
+ if (getBorderValues(tblBorder, tblBorderBrush, tblBorderStyle)) {
+ tableBorder = tblBorder[0];
+ if (tblBorderBrush[0].color().isValid())
+ borderBrush = tblBorderBrush[0];
+ if (tblBorderStyle[0] != static_cast<QTextFrameFormat::BorderStyle>(-1))
+ borderStyle = tblBorderStyle[0];
+ }
+ }
+ break;
case QCss::BorderCollapse:
borderCollapse = decl.borderCollapseValue();
break;
@@ -1233,10 +1250,10 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
}
break;
case QCss::QtBlockIndent:
- blockFormat.setIndent(decl.d->values.first().variant.toInt());
+ blockFormat.setIndent(decl.d->values.constFirst().variant.toInt());
break;
case QCss::QtLineHeightType: {
- QString lineHeightTypeName = decl.d->values.first().variant.toString();
+ QString lineHeightTypeName = decl.d->values.constFirst().variant.toString();
QTextBlockFormat::LineHeightTypes lineHeightType;
if (lineHeightTypeName.compare("proportional"_L1, Qt::CaseInsensitive) == 0)
lineHeightType = QTextBlockFormat::ProportionalHeight;
@@ -1265,7 +1282,7 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
lineHeightType = QTextBlockFormat::MinimumHeight;
} else {
bool ok;
- QCss::Value cssValue = decl.d->values.first();
+ QCss::Value cssValue = decl.d->values.constFirst();
QString value = cssValue.toString();
lineHeight = value.toDouble(&ok);
if (ok) {
@@ -1297,19 +1314,19 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
hasCssListIndent = true;
break;
case QCss::QtParagraphType:
- if (decl.d->values.first().variant.toString().compare("empty"_L1, Qt::CaseInsensitive) == 0)
+ if (decl.d->values.constFirst().variant.toString().compare("empty"_L1, Qt::CaseInsensitive) == 0)
isEmptyParagraph = true;
break;
case QCss::QtTableType:
- if (decl.d->values.first().variant.toString().compare("frame"_L1, Qt::CaseInsensitive) == 0)
+ if (decl.d->values.constFirst().variant.toString().compare("frame"_L1, Qt::CaseInsensitive) == 0)
isTextFrame = true;
- else if (decl.d->values.first().variant.toString().compare("root"_L1, Qt::CaseInsensitive) == 0) {
+ else if (decl.d->values.constFirst().variant.toString().compare("root"_L1, Qt::CaseInsensitive) == 0) {
isTextFrame = true;
isRootFrame = true;
}
break;
case QCss::QtUserState:
- userState = decl.d->values.first().variant.toInt();
+ userState = decl.d->values.constFirst().variant.toInt();
break;
case QCss::Whitespace:
switch (identifier) {
@@ -1363,10 +1380,10 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
setListStyle(decl.d->values);
break;
case QCss::QtListNumberPrefix:
- textListNumberPrefix = decl.d->values.first().variant.toString();
+ textListNumberPrefix = decl.d->values.constFirst().variant.toString();
break;
case QCss::QtListNumberSuffix:
- textListNumberSuffix = decl.d->values.first().variant.toString();
+ textListNumberSuffix = decl.d->values.constFirst().variant.toString();
break;
case QCss::TextAlignment:
switch (identifier) {
@@ -1381,12 +1398,98 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
{
if (resourceProvider != nullptr && QTextDocumentPrivate::get(resourceProvider) != nullptr) {
bool ok;
- qint64 searchKey = decl.d->values.first().variant.toLongLong(&ok);
+ qint64 searchKey = decl.d->values.constFirst().variant.toLongLong(&ok);
if (ok)
applyForegroundImage(searchKey, resourceProvider);
}
break;
}
+ case QCss::QtStrokeColor:
+ {
+ QPen pen = charFormat.textOutline();
+ pen.setStyle(Qt::SolidLine);
+ pen.setColor(decl.colorValue());
+ charFormat.setTextOutline(pen);
+ break;
+ }
+ case QCss::QtStrokeWidth:
+ {
+ qreal width;
+ if (decl.realValue(&width, "px")) {
+ QPen pen = charFormat.textOutline();
+ pen.setWidthF(width);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtStrokeLineCap:
+ {
+ QPen pen = charFormat.textOutline();
+ switch (identifier) {
+ case QCss::Value_SquareCap: pen.setCapStyle(Qt::SquareCap); break;
+ case QCss::Value_FlatCap: pen.setCapStyle(Qt::FlatCap); break;
+ case QCss::Value_RoundCap: pen.setCapStyle(Qt::RoundCap); break;
+ default: break;
+ }
+ charFormat.setTextOutline(pen);
+ break;
+ }
+ case QCss::QtStrokeLineJoin:
+ {
+ QPen pen = charFormat.textOutline();
+ switch (identifier) {
+ case QCss::Value_MiterJoin: pen.setJoinStyle(Qt::MiterJoin); break;
+ case QCss::Value_BevelJoin: pen.setJoinStyle(Qt::BevelJoin); break;
+ case QCss::Value_RoundJoin: pen.setJoinStyle(Qt::RoundJoin); break;
+ case QCss::Value_SvgMiterJoin: pen.setJoinStyle(Qt::SvgMiterJoin); break;
+ default: break;
+ }
+ charFormat.setTextOutline(pen);
+ break;
+ }
+ case QCss::QtStrokeMiterLimit:
+ {
+ qreal miterLimit;
+ if (decl.realValue(&miterLimit)) {
+ QPen pen = charFormat.textOutline();
+ pen.setMiterLimit(miterLimit);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtStrokeDashArray:
+ {
+ QList<qreal> dashes = decl.dashArray();
+ if (!dashes.empty()) {
+ QPen pen = charFormat.textOutline();
+ pen.setDashPattern(dashes);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtStrokeDashOffset:
+ {
+ qreal dashOffset;
+ if (decl.realValue(&dashOffset)) {
+ QPen pen = charFormat.textOutline();
+ pen.setDashOffset(dashOffset);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtForeground:
+ {
+ QBrush brush = decl.brushValue();
+ charFormat.setForeground(brush);
+ break;
+ }
+ case QCss::MaximumWidth:
+ if (id == Html_img) {
+ auto imageFormat = charFormat.toImageFormat();
+ imageFormat.setMaximumWidth(extractor.textLength(decl));
+ charFormat = imageFormat;
+ }
+ break;
default: break;
}
}
@@ -1594,10 +1697,9 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
node->charFormat.setProperty(QTextFormat::FontSizeAdjustment, n);
} else if (key == "face"_L1) {
if (value.contains(u',')) {
- const QStringList values = value.split(u',');
QStringList families;
- for (const QString &family : values)
- families << family.trimmed();
+ for (auto family : value.tokenize(u','))
+ families << family.trimmed().toString();
node->charFormat.setFontFamilies(families);
} else {
node->charFormat.setFontFamilies(QStringList(value));
@@ -1698,7 +1800,8 @@ void QTextHtmlParser::applyAttributes(const QStringList &attributes)
}
break;
case Html_table:
- if (key == "border"_L1) {
+ // If table border already set through css style, prefer that one otherwise consider this value
+ if (key == "border"_L1 && !node->tableBorder) {
setFloatAttribute(&node->tableBorder, value);
} else if (key == "bgcolor"_L1) {
QColor c = QColor::fromString(value);
@@ -2080,7 +2183,7 @@ QList<QCss::Declaration> standardDeclarationForNode(const QTextHtmlParserNode &n
decl.d->propertyId = QCss::FontFamily;
QList<QCss::Value> values;
val.type = QCss::Value::String;
- val.variant = QFontDatabase::systemFont(QFontDatabase::FixedFont).families().first();
+ val.variant = QFontDatabase::systemFont(QFontDatabase::FixedFont).families().constFirst();
values << val;
decl.d->values = values;
decl.d->inheritable = true;
diff --git a/src/gui/text/qtexthtmlparser_p.h b/src/gui/text/qtexthtmlparser_p.h
index ecbc238171..dd52baa23e 100644
--- a/src/gui/text/qtexthtmlparser_p.h
+++ b/src/gui/text/qtexthtmlparser_p.h
@@ -276,7 +276,7 @@ public:
void parse(const QString &text, const QTextDocument *resourceProvider);
- static int lookupElement(const QString &element);
+ static int lookupElement(QStringView element);
Q_GUI_EXPORT static QString parseEntity(QStringView entity);
diff --git a/src/gui/text/qtextimagehandler.cpp b/src/gui/text/qtextimagehandler.cpp
index 70e8961467..920e6c689c 100644
--- a/src/gui/text/qtextimagehandler.cpp
+++ b/src/gui/text/qtextimagehandler.cpp
@@ -12,6 +12,7 @@
#include <private/qtextengine_p.h>
#include <qpalette.h>
#include <qthread.h>
+#include <limits>
QT_BEGIN_NAMESPACE
@@ -23,12 +24,14 @@ static inline QString findAtNxFileOrResource(const QString &baseFileName,
{
// qt_findAtNxFile expects a file name that can be tested with QFile::exists.
// so if the format.name() is a file:/ or qrc:/ URL, then we need to strip away the schema.
- QString localFile = baseFileName;
- if (localFile.startsWith("file:/"_L1))
- localFile = localFile.sliced(6);
- else if (localFile.startsWith("qrc:/"_L1))
- localFile = localFile.sliced(3);
-
+ QString localFile;
+ const QUrl url(baseFileName);
+ if (url.isLocalFile())
+ localFile = url.toLocalFile();
+ else if (baseFileName.startsWith("qrc:/"_L1))
+ localFile = baseFileName.sliced(3);
+ else
+ localFile = baseFileName;
extern QString qt_findAtNxFile(const QString &baseFileName, qreal targetDevicePixelRatio,
qreal *sourceDevicePixelRatio);
return qt_findAtNxFile(localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
@@ -70,21 +73,40 @@ template<typename T>
static QSize getSize(QTextDocument *doc, const QTextImageFormat &format)
{
const bool hasWidth = format.hasProperty(QTextFormat::ImageWidth);
- const int width = qRound(format.width());
+ int width = qRound(format.width());
const bool hasHeight = format.hasProperty(QTextFormat::ImageHeight);
const int height = qRound(format.height());
+ const bool hasMaxWidth = format.hasProperty(QTextFormat::ImageMaxWidth);
+ const auto maxWidth = format.maximumWidth();
+
+ int effectiveMaxWidth = std::numeric_limits<int>::max();
+ if (hasMaxWidth) {
+ if (maxWidth.type() == QTextLength::PercentageLength)
+ effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(100) / 100;
+ else
+ effectiveMaxWidth = maxWidth.rawValue();
+
+ width = qMin(effectiveMaxWidth, width);
+ }
+
T source;
QSize size(width, height);
if (!hasWidth || !hasHeight) {
source = getAs<T>(doc, format);
- const QSizeF sourceSize = source.deviceIndependentSize();
+ QSizeF sourceSize = source.deviceIndependentSize();
+
+ if (sourceSize.width() > effectiveMaxWidth) {
+ // image is bigger than effectiveMaxWidth, scale it down
+ sourceSize.setHeight(effectiveMaxWidth * (sourceSize.height() / qreal(sourceSize.width())));
+ sourceSize.setWidth(effectiveMaxWidth);
+ }
if (!hasWidth) {
if (!hasHeight)
size.setWidth(sourceSize.width());
else
- size.setWidth(qRound(height * (sourceSize.width() / qreal(sourceSize.height()))));
+ size.setWidth(qMin(effectiveMaxWidth, qRound(height * (sourceSize.width() / qreal(sourceSize.height())))));
}
if (!hasHeight) {
if (!hasWidth)
diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp
index 611369951c..f0c7dd24e5 100644
--- a/src/gui/text/qtextlayout.cpp
+++ b/src/gui/text/qtextlayout.cpp
@@ -1043,12 +1043,9 @@ QList<QGlyphRun> QTextLayout::glyphRuns(int from,
for (int i=0; i<d->lines.size(); ++i) {
if (d->lines.at(i).from > from + length)
break;
- else if (d->lines.at(i).from + d->lines[i].length >= from) {
- QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
-
- for (int j = 0; j < glyphRuns.size(); j++) {
- const QGlyphRun &glyphRun = glyphRuns.at(j);
-
+ else if (d->lines.at(i).from + d->lines.at(i).length >= from) {
+ const QList<QGlyphRun> glyphRuns = QTextLine(i, d).glyphRuns(from, length, retrievalFlags);
+ for (const QGlyphRun &glyphRun : glyphRuns) {
QRawFont rawFont = glyphRun.rawFont();
QFontEngine *fontEngine = rawFont.d->fontEngine;
@@ -1107,7 +1104,6 @@ void QTextLayout::draw(QPainter *p, const QPointF &pos, const QList<FormatRange>
int firstLine = 0;
int lastLine = d->lines.size();
for (int i = 0; i < d->lines.size(); ++i) {
- QTextLine l(i, d);
const QScriptLine &sl = d->lines.at(i);
if (sl.y > clipe) {
@@ -1656,7 +1652,7 @@ void QTextLine::setNumColumns(int numColumns)
void QTextLine::setNumColumns(int numColumns, qreal alignmentWidth)
{
QScriptLine &line = eng->lines[index];
- line.width = QFixed::fromReal(alignmentWidth);
+ line.width = QFixed::fromReal(qBound(0.0, alignmentWidth, qreal(QFIXED_MAX)));
line.length = 0;
line.textWidth = 0;
layout_helper(numColumns);
@@ -1672,23 +1668,18 @@ namespace {
struct LineBreakHelper
{
- LineBreakHelper()
- : glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(nullptr), logClusters(nullptr),
- manualWrap(false), whiteSpaceOrObject(true)
- {
- }
-
+ LineBreakHelper() = default;
QScriptLine tmpData;
QScriptLine spaceData;
QGlyphLayout glyphs;
- int glyphCount;
- int maxGlyphs;
- int currentPosition;
- glyph_t previousGlyph;
- QFontEngine *previousGlyphFontEngine;
+ int glyphCount = 0;
+ int maxGlyphs = 0;
+ int currentPosition = 0;
+ glyph_t previousGlyph = 0;
+ QExplicitlySharedDataPointer<QFontEngine> previousGlyphFontEngine;
QFixed minw;
QFixed currentSoftHyphenWidth;
@@ -1696,11 +1687,11 @@ namespace {
QFixed rightBearing;
QFixed minimumRightBearing;
- QFontEngine *fontEngine;
- const unsigned short *logClusters;
+ QExplicitlySharedDataPointer<QFontEngine> fontEngine;
+ const unsigned short *logClusters = nullptr;
- bool manualWrap;
- bool whiteSpaceOrObject;
+ bool manualWrap = false;
+ bool whiteSpaceOrObject = true;
bool checkFullOtherwiseExtend(QScriptLine &line);
@@ -1744,13 +1735,13 @@ namespace {
{
if (currentPosition <= 0)
return;
- calculateRightBearing(fontEngine, currentGlyph());
+ calculateRightBearing(fontEngine.data(), currentGlyph());
}
inline void calculateRightBearingForPreviousGlyph()
{
if (previousGlyph > 0)
- calculateRightBearing(previousGlyphFontEngine, previousGlyph);
+ calculateRightBearing(previousGlyphFontEngine.data(), previousGlyph);
}
static const QFixed RightBearingNotCalculated;
@@ -2232,20 +2223,20 @@ int QTextLine::textLength() const
return eng->lines.at(index).length + eng->lines.at(index).trailingSpaces;
}
-static void setPenAndDrawBackground(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf, const QRectF &r)
+static void drawBackground(QPainter *p, const QTextCharFormat &chf, const QRectF &r)
{
- QBrush c = chf.foreground();
- if (c.style() == Qt::NoBrush) {
- p->setPen(defaultPen);
- }
-
QBrush bg = chf.background();
if (bg.style() != Qt::NoBrush && !chf.property(SuppressBackground).toBool())
p->fillRect(r.toAlignedRect(), bg);
- if (c.style() != Qt::NoBrush) {
- p->setPen(QPen(c, 0));
- }
+}
+static void setPen(QPainter *p, const QPen &defaultPen, const QTextCharFormat &chf)
+{
+ QBrush c = chf.foreground();
+ if (c.style() == Qt::NoBrush)
+ p->setPen(defaultPen);
+ else
+ p->setPen(QPen(c, 0));
}
#if !defined(QT_NO_RAWFONT)
@@ -2640,7 +2631,6 @@ void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
Q_ASSERT(!eng->useRawFont);
#endif
const QScriptLine &line = eng->lines[index];
- QPen pen = p->pen();
bool noText = (selection && selection->format.property(SuppressText).toBool());
@@ -2652,8 +2642,7 @@ void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
const qreal lineHeight = line.height().toReal();
QRectF r(origPos.x() + line.x.toReal(), origPos.y() + line.y.toReal(),
lineHeight / 2, QFontMetrics(eng->font()).horizontalAdvance(u' '));
- setPenAndDrawBackground(p, QPen(), selection->format, r);
- p->setPen(pen);
+ drawBackground(p, selection->format, r);
}
return;
}
@@ -2666,7 +2655,7 @@ void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
else
p->translate(origPos);
- QTextLineItemIterator iterator(eng, index, pos, selection);
+
QFixed lineBase = line.base();
eng->clearDecorations();
eng->enableDelayDecorations();
@@ -2676,183 +2665,207 @@ void QTextLine::draw_internal(QPainter *p, const QPointF &origPos,
const QTextFormatCollection *formatCollection = eng->formatCollection();
bool suppressColors = (eng->option.flags() & QTextOption::SuppressColors);
- while (!iterator.atEnd()) {
- QScriptItem &si = iterator.next();
-
- if (selection && selection->start >= 0 && iterator.isOutsideSelection())
- continue;
- if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
- && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
- continue;
-
- QFixed itemBaseLine = y;
- QFont f = eng->font(si);
- QTextCharFormat format;
- if (formatCollection != nullptr)
- format = formatCollection->defaultTextFormat();
+ auto prepareFormat = [suppressColors, selection, this](QTextCharFormat &format,
+ QScriptItem *si) {
+ format.merge(eng->format(si));
- if (eng->hasFormats() || selection || formatCollection) {
- format.merge(eng->format(&si));
+ if (suppressColors) {
+ format.clearForeground();
+ format.clearBackground();
+ format.clearProperty(QTextFormat::TextUnderlineColor);
+ }
+ if (selection)
+ format.merge(selection->format);
+ };
- if (suppressColors) {
- format.clearForeground();
- format.clearBackground();
- format.clearProperty(QTextFormat::TextUnderlineColor);
- }
- if (selection)
- format.merge(selection->format);
-
- setPenAndDrawBackground(p, pen, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
- iterator.itemWidth.toReal(), line.height().toReal()));
-
- const qreal baseLineOffset = format.baselineOffset() / 100.0;
- QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
- if (valign == QTextCharFormat::AlignSuperScript
- || valign == QTextCharFormat::AlignSubScript
- || !qFuzzyIsNull(baseLineOffset))
- {
- QFontEngine *fe = f.d->engineForScript(si.analysis.script);
- QFixed height = fe->ascent() + fe->descent();
- itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
-
- if (valign == QTextCharFormat::AlignSubScript)
- itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
- else if (valign == QTextCharFormat::AlignSuperScript)
- itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
+ {
+ QTextLineItemIterator iterator(eng, index, pos, selection);
+ while (!iterator.atEnd()) {
+ QScriptItem &si = iterator.next();
+
+ if (eng->hasFormats() || selection || formatCollection) {
+ QTextCharFormat format;
+ if (formatCollection != nullptr)
+ format = formatCollection->defaultTextFormat();
+ prepareFormat(format, &si);
+ drawBackground(p, format, QRectF(iterator.x.toReal(), (y - lineBase).toReal(),
+ iterator.itemWidth.toReal(), line.height().toReal()));
}
}
+ }
- if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
+ QPen pen = p->pen();
+ {
+ QTextLineItemIterator iterator(eng, index, pos, selection);
+ while (!iterator.atEnd()) {
+ QScriptItem &si = iterator.next();
- if (eng->hasFormats()) {
- p->save();
- if (si.analysis.flags == QScriptAnalysis::Object && QTextDocumentPrivate::get(eng->block)) {
- QFixed itemY = y - si.ascent;
- switch (format.verticalAlignment()) {
- case QTextCharFormat::AlignTop:
- itemY = y - lineBase;
- break;
- case QTextCharFormat::AlignMiddle:
- itemY = y - lineBase + (line.height() - si.height()) / 2;
- break;
- case QTextCharFormat::AlignBottom:
- itemY = y - lineBase + line.height() - si.height();
- break;
- default:
- break;
- }
+ if (selection && selection->start >= 0 && iterator.isOutsideSelection())
+ continue;
- QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
-
- eng->docLayout()->drawInlineObject(p, itemRect,
- QTextInlineObject(iterator.item, eng),
- si.position + eng->block.position(),
- format);
- if (selection) {
- QBrush bg = format.brushProperty(ObjectSelectionBrush);
- if (bg.style() != Qt::NoBrush) {
- QColor c = bg.color();
- c.setAlpha(128);
- p->fillRect(itemRect, c);
- }
- }
- } else { // si.isTab
- QFont f = eng->font(si);
- QTextItemInt gf(si, &f, format);
- gf.chars = nullptr;
- gf.num_chars = 0;
- gf.width = iterator.itemWidth;
- QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
- if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
- const QChar visualTab = QChar(QChar::VisualTabCharacter);
- int w = QFontMetrics(f).horizontalAdvance(visualTab);
- qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
- if (x < 0)
- p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
- iterator.itemWidth.toReal(), line.height().toReal()),
- Qt::IntersectClip);
- else
- x /= 2; // Centered
- p->setFont(f);
- p->drawText(QPointF(iterator.x.toReal() + x,
- y.toReal()), visualTab);
- }
+ if (si.analysis.flags == QScriptAnalysis::LineOrParagraphSeparator
+ && !(eng->option.flags() & QTextOption::ShowLineAndParagraphSeparators))
+ continue;
+ QFixed itemBaseLine = y;
+ QFont f = eng->font(si);
+ QTextCharFormat format;
+ if (formatCollection != nullptr)
+ format = formatCollection->defaultTextFormat();
+
+ if (eng->hasFormats() || selection || formatCollection) {
+ prepareFormat(format, &si);
+ setPen(p, pen, format);
+
+ const qreal baseLineOffset = format.baselineOffset() / 100.0;
+ QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
+ if (valign == QTextCharFormat::AlignSuperScript
+ || valign == QTextCharFormat::AlignSubScript
+ || !qFuzzyIsNull(baseLineOffset))
+ {
+ QFontEngine *fe = f.d->engineForScript(si.analysis.script);
+ QFixed height = fe->ascent() + fe->descent();
+ itemBaseLine -= height * QFixed::fromReal(baseLineOffset);
+
+ if (valign == QTextCharFormat::AlignSubScript)
+ itemBaseLine += height * QFixed::fromReal(format.subScriptBaseline() / 100.0);
+ else if (valign == QTextCharFormat::AlignSuperScript)
+ itemBaseLine -= height * QFixed::fromReal(format.superScriptBaseline() / 100.0);
}
- p->restore();
}
- continue;
- }
+ if (si.analysis.flags >= QScriptAnalysis::TabOrObject) {
- unsigned short *logClusters = eng->logClusters(&si);
- QGlyphLayout glyphs = eng->shapedGlyphs(&si);
+ if (eng->hasFormats()) {
+ p->save();
+ if (si.analysis.flags == QScriptAnalysis::Object && QTextDocumentPrivate::get(eng->block)) {
+ QFixed itemY = y - si.ascent;
+ switch (format.verticalAlignment()) {
+ case QTextCharFormat::AlignTop:
+ itemY = y - lineBase;
+ break;
+ case QTextCharFormat::AlignMiddle:
+ itemY = y - lineBase + (line.height() - si.height()) / 2;
+ break;
+ case QTextCharFormat::AlignBottom:
+ itemY = y - lineBase + line.height() - si.height();
+ break;
+ default:
+ break;
+ }
- QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
- &f, eng->layoutData->string.unicode() + iterator.itemStart,
- iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
- gf.logClusters = logClusters + iterator.itemStart - si.position;
- gf.width = iterator.itemWidth;
- gf.justified = line.justified;
- gf.initWithScriptItem(si);
-
- Q_ASSERT(gf.fontEngine);
-
- QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
- if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
- QPainterPath path;
- path.setFillRule(Qt::WindingFill);
-
- if (gf.glyphs.numGlyphs)
- gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
- if (gf.flags) {
- const QFontEngine *fe = gf.fontEngine;
- const qreal lw = fe->lineThickness().toReal();
- if (gf.flags & QTextItem::Underline) {
- qreal offs = fe->underlinePosition().toReal();
- path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
- }
- if (gf.flags & QTextItem::Overline) {
- qreal offs = fe->ascent().toReal() + 1;
- path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
- }
- if (gf.flags & QTextItem::StrikeOut) {
- qreal offs = fe->ascent().toReal() / 3;
- path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+ QRectF itemRect(iterator.x.toReal(), itemY.toReal(), iterator.itemWidth.toReal(), si.height().toReal());
+
+ eng->docLayout()->drawInlineObject(p, itemRect,
+ QTextInlineObject(iterator.item, eng),
+ si.position + eng->block.position(),
+ format);
+ if (selection) {
+ QBrush bg = format.brushProperty(ObjectSelectionBrush);
+ if (bg.style() != Qt::NoBrush) {
+ QColor c = bg.color();
+ c.setAlpha(128);
+ p->fillRect(itemRect, c);
+ }
+ }
+ } else { // si.isTab
+ QFont f = eng->font(si);
+ QTextItemInt gf(si, &f, format);
+ gf.chars = nullptr;
+ gf.num_chars = 0;
+ gf.width = iterator.itemWidth;
+ QPainterPrivate::get(p)->drawTextItem(QPointF(iterator.x.toReal(), y.toReal()), gf, eng);
+ if (eng->option.flags() & QTextOption::ShowTabsAndSpaces) {
+ const QChar visualTab = QChar(QChar::VisualTabCharacter);
+ int w = QFontMetrics(f).horizontalAdvance(visualTab);
+ qreal x = iterator.itemWidth.toReal() - w; // Right-aligned
+ if (x < 0)
+ p->setClipRect(QRectF(iterator.x.toReal(), line.y.toReal(),
+ iterator.itemWidth.toReal(), line.height().toReal()),
+ Qt::IntersectClip);
+ else
+ x /= 2; // Centered
+ p->setFont(f);
+ p->drawText(QPointF(iterator.x.toReal() + x,
+ y.toReal()), visualTab);
+ }
+
+ }
+ p->restore();
}
+
+ continue;
}
- p->save();
- p->setRenderHint(QPainter::Antialiasing);
- //Currently QPen with a Qt::NoPen style still returns a default
- //QBrush which != Qt::NoBrush so we need this specialcase to reset it
- if (p->pen().style() == Qt::NoPen)
- p->setBrush(Qt::NoBrush);
- else
- p->setBrush(p->pen().brush());
+ unsigned short *logClusters = eng->logClusters(&si);
+ QGlyphLayout glyphs = eng->shapedGlyphs(&si);
- p->setPen(format.textOutline());
- p->drawPath(path);
- p->restore();
- } else {
- if (noText)
- gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
- QPainterPrivate::get(p)->drawTextItem(pos, gf, eng);
- }
+ QTextItemInt gf(glyphs.mid(iterator.glyphsStart, iterator.glyphsEnd - iterator.glyphsStart),
+ &f, eng->layoutData->string.unicode() + iterator.itemStart,
+ iterator.itemEnd - iterator.itemStart, eng->fontEngine(si), format);
+ gf.logClusters = logClusters + iterator.itemStart - si.position;
+ gf.width = iterator.itemWidth;
+ gf.justified = line.justified;
+ gf.initWithScriptItem(si);
+
+ Q_ASSERT(gf.fontEngine);
+
+ QPointF pos(iterator.x.toReal(), itemBaseLine.toReal());
+ if (format.penProperty(QTextFormat::TextOutline).style() != Qt::NoPen) {
+ QPainterPath path;
+ path.setFillRule(Qt::WindingFill);
+
+ if (gf.glyphs.numGlyphs)
+ gf.fontEngine->addOutlineToPath(pos.x(), pos.y(), gf.glyphs, &path, gf.flags);
+ if (gf.flags) {
+ const QFontEngine *fe = gf.fontEngine;
+ const qreal lw = fe->lineThickness().toReal();
+ if (gf.flags & QTextItem::Underline) {
+ qreal offs = fe->underlinePosition().toReal();
+ path.addRect(pos.x(), pos.y() + offs, gf.width.toReal(), lw);
+ }
+ if (gf.flags & QTextItem::Overline) {
+ qreal offs = fe->ascent().toReal() + 1;
+ path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+ }
+ if (gf.flags & QTextItem::StrikeOut) {
+ qreal offs = fe->ascent().toReal() / 3;
+ path.addRect(pos.x(), pos.y() - offs, gf.width.toReal(), lw);
+ }
+ }
- if ((si.analysis.flags == QScriptAnalysis::Space
- || si.analysis.flags == QScriptAnalysis::Nbsp)
- && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
- QBrush c = format.foreground();
- if (c.style() != Qt::NoBrush)
- p->setPen(c.color());
- const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
- QFont oldFont = p->font();
- p->setFont(eng->font(si));
- p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
- p->setPen(pen);
- p->setFont(oldFont);
+ p->save();
+ p->setRenderHint(QPainter::Antialiasing);
+ //Currently QPen with a Qt::NoPen style still returns a default
+ //QBrush which != Qt::NoBrush so we need this specialcase to reset it
+ if (p->pen().style() == Qt::NoPen)
+ p->setBrush(Qt::NoBrush);
+ else
+ p->setBrush(p->pen().brush());
+
+ p->setPen(format.textOutline());
+ p->drawPath(path);
+ p->restore();
+ } else {
+ if (noText)
+ gf.glyphs.numGlyphs = 0; // slightly less elegant than it should be
+ QPainterPrivate::get(p)->drawTextItem(pos, gf, eng);
+ }
+
+ if ((si.analysis.flags == QScriptAnalysis::Space
+ || si.analysis.flags == QScriptAnalysis::Nbsp)
+ && (eng->option.flags() & QTextOption::ShowTabsAndSpaces)) {
+ QBrush c = format.foreground();
+ if (c.style() != Qt::NoBrush)
+ p->setPen(c.color());
+ const QChar visualSpace = si.analysis.flags == QScriptAnalysis::Space ? u'\xb7' : u'\xb0';
+ QFont oldFont = p->font();
+ p->setFont(eng->font(si));
+ p->drawText(QPointF(iterator.x.toReal(), itemBaseLine.toReal()), visualSpace);
+ p->setPen(pen);
+ p->setFont(oldFont);
+ }
}
}
eng->drawDecorations(p);
diff --git a/src/gui/text/qtextlist.cpp b/src/gui/text/qtextlist.cpp
index 90a6cf8867..7ec8b6215e 100644
--- a/src/gui/text/qtextlist.cpp
+++ b/src/gui/text/qtextlist.cpp
@@ -186,16 +186,14 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
if (itemNumber < 1) {
result = QString::number(itemNumber);
} else if (itemNumber < 5000) {
- QByteArray romanNumeral;
+ QString romanNumeral;
// works for up to 4999 items
- static const char romanSymbolsLower[] = "iiivixxxlxcccdcmmmm";
- static const char romanSymbolsUpper[] = "IIIVIXXXLXCCCDCMMMM";
- QByteArray romanSymbols; // wrap to have "mid"
+ QLatin1StringView romanSymbols;
if (style == QTextListFormat::ListLowerRoman)
- romanSymbols = QByteArray::fromRawData(romanSymbolsLower, sizeof(romanSymbolsLower));
+ romanSymbols = "iiivixxxlxcccdcmmmm"_L1;
else
- romanSymbols = QByteArray::fromRawData(romanSymbolsUpper, sizeof(romanSymbolsUpper));
+ romanSymbols = "IIIVIXXXLXCCCDCMMMM"_L1;
int c[] = { 1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000 };
int n = itemNumber;
@@ -220,10 +218,10 @@ QString QTextList::itemText(const QTextBlock &blockIt) const
numDigits = q;
}
- romanNumeral.append(romanSymbols.mid(startDigit, numDigits));
+ romanNumeral.append(romanSymbols.sliced(startDigit, numDigits));
}
}
- result = QString::fromLatin1(romanNumeral);
+ result = std::move(romanNumeral);
} else {
result = u"?"_s;
}
diff --git a/src/gui/text/qtextmarkdownimporter.cpp b/src/gui/text/qtextmarkdownimporter.cpp
index b43e1583cf..e7fcad67b5 100644
--- a/src/gui/text/qtextmarkdownimporter.cpp
+++ b/src/gui/text/qtextmarkdownimporter.cpp
@@ -27,6 +27,8 @@ Q_LOGGING_CATEGORY(lcMD, "qt.text.markdown")
static const QChar qtmi_Newline = u'\n';
static const QChar qtmi_Space = u' ';
+static constexpr auto markerString() noexcept { return "---"_L1; }
+
// TODO maybe eliminate the margins after all views recognize BlockQuoteLevel, CSS can format it, etc.
static const int qtmi_BlockQuoteIndent =
40; // pixels, same as in QTextHtmlParserNode::initializeProperties
@@ -46,7 +48,8 @@ static_assert(int(QTextMarkdownImporter::FeaturePermissiveAutoLinks) == MD_FLAG_
static_assert(int(QTextMarkdownImporter::FeatureTasklists) == MD_FLAG_TASKLISTS);
static_assert(int(QTextMarkdownImporter::FeatureNoHTML) == MD_FLAG_NOHTML);
static_assert(int(QTextMarkdownImporter::DialectCommonMark) == MD_DIALECT_COMMONMARK);
-static_assert(int(QTextMarkdownImporter::DialectGitHub) == (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE));
+static_assert(int(QTextMarkdownImporter::DialectGitHub) ==
+ (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE | QTextMarkdownImporter::FeatureFrontMatter));
// --------------------------------------------------------
// MD4C callback function wrappers
@@ -104,18 +107,19 @@ static Qt::Alignment MdAlignment(MD_ALIGN a, Qt::Alignment defaultAlignment = Qt
}
}
-QTextMarkdownImporter::QTextMarkdownImporter(QTextMarkdownImporter::Features features)
- : m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
+QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextMarkdownImporter::Features features)
+ : m_cursor(doc)
+ , m_monoFont(QFontDatabase::systemFont(QFontDatabase::FixedFont))
, m_features(features)
{
}
-QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument::MarkdownFeatures features)
- : QTextMarkdownImporter(static_cast<QTextMarkdownImporter::Features>(int(features)))
+QTextMarkdownImporter::QTextMarkdownImporter(QTextDocument *doc, QTextDocument::MarkdownFeatures features)
+ : QTextMarkdownImporter(doc, static_cast<QTextMarkdownImporter::Features>(int(features)))
{
}
-void QTextMarkdownImporter::import(QTextDocument *doc, const QString &markdown)
+void QTextMarkdownImporter::import(const QString &markdown)
{
MD_PARSER callbacks = {
0, // abi_version
@@ -128,21 +132,36 @@ void QTextMarkdownImporter::import(QTextDocument *doc, const QString &markdown)
&CbDebugLog,
nullptr // syntax
};
- m_doc = doc;
- m_paragraphMargin = m_doc->defaultFont().pointSize() * 2 / 3;
- m_cursor = new QTextCursor(doc);
+ QTextDocument *doc = m_cursor.document();
+ const auto defaultFont = doc->defaultFont();
+ m_paragraphMargin = defaultFont.pointSize() * 2 / 3;
doc->clear();
- if (doc->defaultFont().pointSize() != -1)
- m_monoFont.setPointSize(doc->defaultFont().pointSize());
+ if (defaultFont.pointSize() != -1)
+ m_monoFont.setPointSize(defaultFont.pointSize());
else
- m_monoFont.setPixelSize(doc->defaultFont().pixelSize());
- qCDebug(lcMD) << "default font" << doc->defaultFont() << "mono font" << m_monoFont;
- QByteArray md = markdown.toUtf8();
- m_cursor->beginEditBlock();
- md_parse(md.constData(), MD_SIZE(md.size()), &callbacks, this);
- m_cursor->endEditBlock();
- delete m_cursor;
- m_cursor = nullptr;
+ m_monoFont.setPixelSize(defaultFont.pixelSize());
+ qCDebug(lcMD) << "default font" << defaultFont << "mono font" << m_monoFont;
+ QStringView md = markdown;
+
+ if (m_features.testFlag(QTextMarkdownImporter::FeatureFrontMatter) && md.startsWith(markerString())) {
+ qsizetype endMarkerPos = md.indexOf(markerString(), markerString().size() + 1);
+ if (endMarkerPos > 4) {
+ qsizetype firstLinePos = 4; // first line of yaml
+ while (md.at(firstLinePos) == '\n'_L1 || md.at(firstLinePos) == '\r'_L1)
+ ++firstLinePos;
+ auto frontMatter = md.sliced(firstLinePos, endMarkerPos - firstLinePos);
+ firstLinePos = endMarkerPos + 4; // first line of markdown after yaml
+ while (md.size() > firstLinePos && (md.at(firstLinePos) == '\n'_L1 || md.at(firstLinePos) == '\r'_L1))
+ ++firstLinePos;
+ md = md.sliced(firstLinePos);
+ doc->setMetaInformation(QTextDocument::FrontMatter, frontMatter.toString());
+ qCDebug(lcMD) << "extracted FrontMatter: size" << frontMatter.size();
+ }
+ }
+ const auto mdUtf8 = md.toUtf8();
+ m_cursor.beginEditBlock();
+ md_parse(mdUtf8.constData(), MD_SIZE(mdUtf8.size()), &callbacks, this);
+ m_cursor.endEditBlock();
}
int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
@@ -181,11 +200,11 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
charFmt.setFontWeight(QFont::Bold);
blockFmt.setHeadingLevel(int(detail->level));
m_needsInsertBlock = false;
- if (m_doc->isEmpty()) {
- m_cursor->setBlockFormat(blockFmt);
- m_cursor->setCharFormat(charFmt);
+ if (m_cursor.document()->isEmpty()) {
+ m_cursor.setBlockFormat(blockFmt);
+ m_cursor.setCharFormat(charFmt);
} else {
- m_cursor->insertBlock(blockFmt, charFmt);
+ m_cursor.insertBlock(blockFmt, charFmt);
}
qCDebug(lcMD, "H%d", detail->level);
} break;
@@ -200,7 +219,7 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
} break;
case MD_BLOCK_UL: {
if (m_needsInsertList) // list nested in an empty list
- m_listStack.push(m_cursor->insertList(m_listFormat));
+ m_listStack.push(m_cursor.insertList(m_listFormat));
else
m_needsInsertList = true;
MD_BLOCK_UL_DETAIL *detail = static_cast<MD_BLOCK_UL_DETAIL *>(det);
@@ -221,7 +240,7 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
} break;
case MD_BLOCK_OL: {
if (m_needsInsertList) // list nested in an empty list
- m_listStack.push(m_cursor->insertList(m_listFormat));
+ m_listStack.push(m_cursor.insertList(m_listFormat));
else
m_needsInsertList = true;
MD_BLOCK_OL_DETAIL *detail = static_cast<MD_BLOCK_OL_DETAIL *>(det);
@@ -242,10 +261,10 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
qWarning("malformed table in Markdown input");
return 1;
}
- *m_cursor = cell.firstCursorPosition();
- QTextBlockFormat blockFmt = m_cursor->blockFormat();
+ m_cursor = cell.firstCursorPosition();
+ QTextBlockFormat blockFmt = m_cursor.blockFormat();
blockFmt.setAlignment(MdAlignment(detail->align));
- m_cursor->setBlockFormat(blockFmt);
+ m_cursor.setBlockFormat(blockFmt);
qCDebug(lcMD) << "TD; align" << detail->align << MdAlignment(detail->align) << "col" << m_tableCol;
} break;
case MD_BLOCK_TH: {
@@ -273,13 +292,13 @@ int QTextMarkdownImporter::cbEnterBlock(int blockType, void *det)
case MD_BLOCK_TABLE:
m_tableColumnCount = 0;
m_tableRowCount = 0;
- m_currentTable = m_cursor->insertTable(1, 1); // we don't know the dimensions yet
+ m_currentTable = m_cursor.insertTable(1, 1); // we don't know the dimensions yet
break;
case MD_BLOCK_HR: {
qCDebug(lcMD, "HR");
QTextBlockFormat blockFmt;
blockFmt.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, 1);
- m_cursor->insertBlock(blockFmt, QTextCharFormat());
+ m_cursor.insertBlock(blockFmt, QTextCharFormat());
} break;
default:
break; // nothing to do for now
@@ -297,7 +316,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
case MD_BLOCK_UL:
case MD_BLOCK_OL:
if (Q_UNLIKELY(m_needsInsertList))
- m_listStack.push(m_cursor->createList(m_listFormat));
+ m_listStack.push(m_cursor.createList(m_listFormat));
if (Q_UNLIKELY(m_listStack.isEmpty())) {
qCWarning(lcMD, "list ended unexpectedly");
} else {
@@ -335,7 +354,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
case MD_BLOCK_TABLE:
qCDebug(lcMD) << "table ended with" << m_currentTable->columns() << "cols and" << m_currentTable->rows() << "rows";
m_currentTable = nullptr;
- m_cursor->movePosition(QTextCursor::End);
+ m_cursor.movePosition(QTextCursor::End);
break;
case MD_BLOCK_LI:
qCDebug(lcMD, "LI at level %d ended", int(m_listStack.size()));
@@ -352,7 +371,7 @@ int QTextMarkdownImporter::cbLeaveBlock(int blockType, void *detail)
m_needsInsertBlock = true;
} break;
case MD_BLOCK_H:
- m_cursor->setCharFormat(QTextCharFormat());
+ m_cursor.setCharFormat(QTextCharFormat());
break;
default:
break;
@@ -403,10 +422,10 @@ int QTextMarkdownImporter::cbEnterSpan(int spanType, void *det)
break;
}
m_spanFormatStack.push(charFmt);
- qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
+ qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().constFirst()
<< charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
<< charFmt.foreground().color().name();
- m_cursor->setCharFormat(charFmt);
+ m_cursor.setCharFormat(charFmt);
return 0; // no error
}
@@ -419,8 +438,8 @@ int QTextMarkdownImporter::cbLeaveSpan(int spanType, void *detail)
if (!m_spanFormatStack.isEmpty())
charFmt = m_spanFormatStack.top();
}
- m_cursor->setCharFormat(charFmt);
- qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().first()
+ m_cursor.setCharFormat(charFmt);
+ qCDebug(lcMD) << spanType << "setCharFormat" << charFmt.font().families().constFirst()
<< charFmt.fontWeight() << (charFmt.fontItalic() ? "italic" : "")
<< charFmt.foreground().color().name();
if (spanType == int(MD_SPAN_IMG))
@@ -464,7 +483,7 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
if (m_htmlTagDepth)
m_htmlAccumulator += s;
else
- m_cursor->insertHtml(s);
+ m_cursor.insertHtml(s);
s = QString();
break;
#endif
@@ -486,11 +505,11 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
m_htmlAccumulator += s;
if (!m_htmlTagDepth) { // all open tags are now closed
qCDebug(lcMD) << "HTML" << m_htmlAccumulator;
- m_cursor->insertHtml(m_htmlAccumulator);
+ m_cursor.insertHtml(m_htmlAccumulator);
if (m_spanFormatStack.isEmpty())
- m_cursor->setCharFormat(QTextCharFormat());
+ m_cursor.setCharFormat(QTextCharFormat());
else
- m_cursor->setCharFormat(m_spanFormatStack.top());
+ m_cursor.setCharFormat(m_spanFormatStack.top());
m_htmlAccumulator = QString();
}
#endif
@@ -520,24 +539,24 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
m_imageFormat.setProperty(QTextFormat::ImageAltText, s);
qCDebug(lcMD) << "image" << m_imageFormat.name()
<< "title" << m_imageFormat.stringProperty(QTextFormat::ImageTitle)
- << "alt" << s << "relative to" << m_doc->baseUrl();
- m_cursor->insertImage(m_imageFormat);
+ << "alt" << s << "relative to" << m_cursor.document()->baseUrl();
+ m_cursor.insertImage(m_imageFormat);
return 0; // no error
}
if (!s.isEmpty())
- m_cursor->insertText(s);
- if (m_cursor->currentList()) {
+ m_cursor.insertText(s);
+ if (m_cursor.currentList()) {
// The list item will indent the list item's text, so we don't need indentation on the block.
- QTextBlockFormat bfmt = m_cursor->blockFormat();
+ QTextBlockFormat bfmt = m_cursor.blockFormat();
bfmt.setIndent(0);
- m_cursor->setBlockFormat(bfmt);
+ m_cursor.setBlockFormat(bfmt);
}
if (lcMD().isEnabled(QtDebugMsg)) {
- QTextBlockFormat bfmt = m_cursor->blockFormat();
+ QTextBlockFormat bfmt = m_cursor.blockFormat();
QString debugInfo;
- if (m_cursor->currentList())
- debugInfo = "in list at depth "_L1 + QString::number(m_cursor->currentList()->format().indent());
+ if (m_cursor.currentList())
+ debugInfo = "in list at depth "_L1 + QString::number(m_cursor.currentList()->format().indent());
if (bfmt.hasProperty(QTextFormat::BlockQuoteLevel))
debugInfo += "in blockquote at depth "_L1 +
QString::number(bfmt.intProperty(QTextFormat::BlockQuoteLevel));
@@ -554,7 +573,7 @@ int QTextMarkdownImporter::cbText(int textType, const char *text, unsigned size)
Insert a new block based on stored state.
m_cursor cannot store the state for the _next_ block ahead of time, because
- m_cursor->setBlockFormat() controls the format of the block that the cursor
+ m_cursor.setBlockFormat() controls the format of the block that the cursor
is already in; so cbLeaveBlock() cannot call setBlockFormat() without
altering the block that was just added. Therefore cbLeaveBlock() and the
following cbEnterBlock() set variables to remember what formatting should
@@ -596,19 +615,19 @@ void QTextMarkdownImporter::insertBlock()
blockFormat.setMarker(m_markerType);
if (!m_listStack.isEmpty())
blockFormat.setIndent(m_listStack.size());
- if (m_doc->isEmpty()) {
- m_cursor->setBlockFormat(blockFormat);
- m_cursor->setCharFormat(charFormat);
+ if (m_cursor.document()->isEmpty()) {
+ m_cursor.setBlockFormat(blockFormat);
+ m_cursor.setCharFormat(charFormat);
} else if (m_listItem) {
- m_cursor->insertBlock(blockFormat, QTextCharFormat());
- m_cursor->setCharFormat(charFormat);
+ m_cursor.insertBlock(blockFormat, QTextCharFormat());
+ m_cursor.setCharFormat(charFormat);
} else {
- m_cursor->insertBlock(blockFormat, charFormat);
+ m_cursor.insertBlock(blockFormat, charFormat);
}
if (m_needsInsertList) {
- m_listStack.push(m_cursor->createList(m_listFormat));
+ m_listStack.push(m_cursor.createList(m_listFormat));
} else if (!m_listStack.isEmpty() && m_listItem && m_listStack.top()) {
- m_listStack.top()->add(m_cursor->block());
+ m_listStack.top()->add(m_cursor.block());
}
m_needsInsertList = false;
m_needsInsertBlock = false;
diff --git a/src/gui/text/qtextmarkdownimporter_p.h b/src/gui/text/qtextmarkdownimporter_p.h
index b7974da56e..8b8f4ec9bb 100644
--- a/src/gui/text/qtextmarkdownimporter_p.h
+++ b/src/gui/text/qtextmarkdownimporter_p.h
@@ -46,6 +46,7 @@ public:
FeaturePermissiveWWWAutoLinks = 0x0400,
FeatureTasklists = 0x0800,
FeatureUnderline = 0x4000,
+ FeatureFrontMatter = 0x100000, // Qt feature, not yet in MD4C
// composite flags
FeaturePermissiveAutoLinks = FeaturePermissiveMailAutoLinks
| FeaturePermissiveURLAutoLinks | FeaturePermissiveWWWAutoLinks,
@@ -55,10 +56,10 @@ public:
};
Q_DECLARE_FLAGS(Features, Feature)
- QTextMarkdownImporter(Features features);
- QTextMarkdownImporter(QTextDocument::MarkdownFeatures features);
+ QTextMarkdownImporter(QTextDocument *doc, Features features);
+ QTextMarkdownImporter(QTextDocument *doc, QTextDocument::MarkdownFeatures features);
- void import(QTextDocument *doc, const QString &markdown);
+ void import(const QString &markdown);
public:
// MD4C callbacks
@@ -72,8 +73,7 @@ private:
void insertBlock();
private:
- QTextDocument *m_doc = nullptr;
- QTextCursor *m_cursor = nullptr;
+ QTextCursor m_cursor;
QTextTable *m_currentTable = nullptr; // because m_cursor->currentTable() doesn't work
#if QT_CONFIG(regularexpression)
QString m_htmlAccumulator;
diff --git a/src/gui/text/qtextmarkdownwriter.cpp b/src/gui/text/qtextmarkdownwriter.cpp
index 5fb67ccc67..361158e722 100644
--- a/src/gui/text/qtextmarkdownwriter.cpp
+++ b/src/gui/text/qtextmarkdownwriter.cpp
@@ -10,7 +10,9 @@
#include "qtexttable.h"
#include "qtextcursor.h"
#include "qtextimagehandler_p.h"
+#include "qtextmarkdownimporter_p.h"
#include "qloggingcategory.h"
+#include <QtCore/QRegularExpression>
#if QT_CONFIG(itemmodel)
#include "qabstractitemmodel.h"
#endif
@@ -38,6 +40,7 @@ QTextMarkdownWriter::QTextMarkdownWriter(QTextStream &stream, QTextDocument::Mar
bool QTextMarkdownWriter::writeAll(const QTextDocument *document)
{
+ writeFrontMatter(document->metaInformation(QTextDocument::FrontMatter));
writeFrame(document->rootFrame());
return true;
}
@@ -76,6 +79,19 @@ void QTextMarkdownWriter::writeTable(const QAbstractItemModel *table)
}
#endif
+void QTextMarkdownWriter::writeFrontMatter(const QString &fm)
+{
+ const bool featureEnabled = m_features.testFlag(
+ static_cast<QTextDocument::MarkdownFeature>(QTextMarkdownImporter::FeatureFrontMatter));
+ qCDebug(lcMDW) << "writing FrontMatter?" << featureEnabled << "size" << fm.size();
+ if (fm.isEmpty() || !featureEnabled)
+ return;
+ m_stream << "---\n"_L1 << fm;
+ if (!fm.endsWith(qtmw_Newline))
+ m_stream << qtmw_Newline;
+ m_stream << "---\n"_L1;
+}
+
void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
{
Q_ASSERT(frame);
@@ -112,17 +128,22 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
// suppress needless blank lines, when there will be a big change in block format
bool nextIsDifferent = false;
bool ending = false;
+ int blockQuoteIndent = 0;
+ int nextBlockQuoteIndent = 0;
{
QTextFrame::iterator next = iterator;
++next;
+ QTextBlockFormat format = iterator.currentBlock().blockFormat();
+ QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
+ blockQuoteIndent = format.intProperty(QTextFormat::BlockQuoteLevel);
+ nextBlockQuoteIndent = nextFormat.intProperty(QTextFormat::BlockQuoteLevel);
if (next.atEnd()) {
nextIsDifferent = true;
ending = true;
} else {
- QTextBlockFormat format = iterator.currentBlock().blockFormat();
- QTextBlockFormat nextFormat = next.currentBlock().blockFormat();
if (nextFormat.indent() != format.indent() ||
- nextFormat.property(QTextFormat::BlockCodeLanguage) != format.property(QTextFormat::BlockCodeLanguage))
+ nextFormat.property(QTextFormat::BlockCodeLanguage) !=
+ format.property(QTextFormat::BlockCodeLanguage))
nextIsDifferent = true;
}
}
@@ -139,8 +160,10 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
tableRow = cell.row();
}
} else if (!block.textList()) {
- if (lastWasList)
+ if (lastWasList) {
m_stream << qtmw_Newline;
+ m_linePrefixWritten = false;
+ }
}
int endingCol = writeBlock(block, !table, table && tableRow == 0,
nextIsDifferent && !block.textList());
@@ -164,8 +187,16 @@ void QTextMarkdownWriter::writeFrame(const QTextFrame *frame)
} else if (endingCol > 0) {
if (block.textList() || block.blockFormat().hasProperty(QTextFormat::BlockCodeLanguage)) {
m_stream << qtmw_Newline;
+ if (block.textList()) {
+ m_stream << m_linePrefix;
+ m_linePrefixWritten = true;
+ }
} else {
- m_stream << qtmw_Newline << qtmw_Newline;
+ m_stream << qtmw_Newline;
+ if (nextBlockQuoteIndent < blockQuoteIndent)
+ setLinePrefixForBlockQuote(nextBlockQuoteIndent);
+ m_stream << m_linePrefix;
+ m_stream << qtmw_Newline;
m_doubleNewlineWritten = true;
}
}
@@ -211,6 +242,16 @@ QTextMarkdownWriter::ListInfo QTextMarkdownWriter::listInfo(QTextList *list)
return m_listInfo.value(list);
}
+void QTextMarkdownWriter::setLinePrefixForBlockQuote(int level)
+{
+ m_linePrefix.clear();
+ if (level > 0) {
+ m_linePrefix.reserve(level * 2);
+ for (int i = 0; i < level; ++i)
+ m_linePrefix += u"> ";
+ }
+}
+
static int nearestWordWrapIndex(const QString &s, int before)
{
before = qMin(before, s.size());
@@ -248,15 +289,53 @@ static int adjacentBackticksCount(const QString &s)
return ret;
}
+/*! \internal
+ Escape anything at the beginning of a line of markdown that would be
+ misinterpreted by a markdown parser, including any period that follows a
+ number (to avoid misinterpretation as a numbered list item).
+ https://spec.commonmark.org/0.31.2/#backslash-escapes
+*/
static void maybeEscapeFirstChar(QString &s)
{
+ static const QRegularExpression numericListRe(uR"(\d+([\.)])\s)"_s);
+ static const QLatin1StringView specialFirstCharacters("#*+-");
+
QString sTrimmed = s.trimmed();
if (sTrimmed.isEmpty())
return;
- char firstChar = sTrimmed.at(0).toLatin1();
- if (firstChar == '*' || firstChar == '+' || firstChar == '-') {
- int i = s.indexOf(QLatin1Char(firstChar));
+ QChar firstChar = sTrimmed.at(0);
+ if (specialFirstCharacters.contains(firstChar)) {
+ int i = s.indexOf(firstChar); // == 0 unless s got trimmed
s.insert(i, u'\\');
+ } else {
+ auto match = numericListRe.match(s, 0, QRegularExpression::NormalMatch,
+ QRegularExpression::AnchorAtOffsetMatchOption);
+ if (match.hasMatch())
+ s.insert(match.capturedStart(1), qtmw_Backslash);
+ }
+}
+
+/*! \internal
+ Escape all backslashes. Then escape any special character that stands
+ alone or prefixes a "word", including the \c < that starts an HTML tag.
+ https://spec.commonmark.org/0.31.2/#backslash-escapes
+*/
+static void escapeSpecialCharacters(QString &s)
+{
+ static const QRegularExpression spaceRe(uR"(\s+)"_s);
+ static const QRegularExpression specialRe(uR"([<!*[`&]+[/\w])"_s);
+
+ s.replace("\\"_L1, "\\\\"_L1);
+
+ int i = 0;
+ while (i >= 0) {
+ if (int j = s.indexOf(specialRe, i); j >= 0) {
+ s.insert(j, qtmw_Backslash);
+ i = j + 3;
+ }
+ i = s.indexOf(spaceRe, i);
+ if (i >= 0)
+ ++i; // past the whitespace, if found
}
}
@@ -336,10 +415,20 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
const bool codeBlock = blockFmt.hasProperty(QTextFormat::BlockCodeFence) ||
blockFmt.stringProperty(QTextFormat::BlockCodeLanguage).size() > 0 ||
blockFmt.nonBreakableLines();
+ const int blockQuoteLevel = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
if (m_fencedCodeBlock && !codeBlock) {
m_stream << m_linePrefix << m_codeBlockFence << qtmw_Newline;
m_fencedCodeBlock = false;
m_codeBlockFence.clear();
+ m_linePrefixWritten = m_linePrefix.size() > 0;
+ }
+ m_linePrefix.clear();
+ if (!blockFmt.headingLevel() && blockQuoteLevel > 0) {
+ setLinePrefixForBlockQuote(blockQuoteLevel);
+ if (!m_linePrefixWritten) {
+ m_stream << m_linePrefix;
+ m_linePrefixWritten = true;
+ }
}
if (block.textList()) { // it's a list-item
auto fmt = block.textList()->format();
@@ -416,31 +505,27 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
if (blockFmt.hasProperty(QTextFormat::BlockIndent))
m_codeBlockFence = QString(m_wrappedLineIndent, qtmw_Space) + m_codeBlockFence;
// A block quote can contain an indented code block, but not vice-versa.
- m_stream << m_linePrefix << m_codeBlockFence
- << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage) << qtmw_Newline;
+ m_stream << m_codeBlockFence << blockFmt.stringProperty(QTextFormat::BlockCodeLanguage)
+ << qtmw_Newline << m_linePrefix;
m_fencedCodeBlock = true;
}
wrap = false;
} else if (!blockFmt.indent()) {
m_wrappedLineIndent = 0;
- m_linePrefix.clear();
- if (blockFmt.hasProperty(QTextFormat::BlockQuoteLevel)) {
- int level = blockFmt.intProperty(QTextFormat::BlockQuoteLevel);
- QString quoteMarker = QStringLiteral("> ");
- m_linePrefix.reserve(level * 2);
- for (int i = 0; i < level; ++i)
- m_linePrefix += quoteMarker;
- }
if (blockFmt.hasProperty(QTextFormat::BlockCodeLanguage)) {
// A block quote can contain an indented code block, but not vice-versa.
m_linePrefix += QString(4, qtmw_Space);
m_indentedCodeBlock = true;
}
+ if (!m_linePrefixWritten) {
+ m_stream << m_linePrefix;
+ m_linePrefixWritten = true;
+ }
}
- if (blockFmt.headingLevel())
+ if (blockFmt.headingLevel()) {
m_stream << QByteArray(blockFmt.headingLevel(), '#') << ' ';
- else
- m_stream << m_linePrefix;
+ wrap = false;
+ }
QString wrapIndentString = m_linePrefix + QString(m_wrappedLineIndent, qtmw_Space);
// It would be convenient if QTextStream had a lineCharPos() accessor,
@@ -453,12 +538,17 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
bool italic = false;
bool underline = false;
bool strikeOut = false;
+ bool endingMarkers = false;
QString backticks(qtmw_Backtick);
for (QTextBlock::Iterator frag = block.begin(); !frag.atEnd(); ++frag) {
missedBlankCodeBlockLine = false;
QString fragmentText = frag.fragment().text();
while (fragmentText.endsWith(qtmw_Newline))
fragmentText.chop(1);
+ if (!(m_fencedCodeBlock || m_indentedCodeBlock)) {
+ escapeSpecialCharacters(fragmentText);
+ maybeEscapeFirstChar(fragmentText);
+ }
if (block.textList()) { // <li>first line</br>continuation</li>
QString newlineIndent =
QString(qtmw_Newline) + QString(m_wrappedLineIndent, qtmw_Space);
@@ -520,26 +610,36 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
if (startsOrEndsWithBacktick)
markers += qtmw_Space;
mono = monoFrag;
+ if (!mono)
+ endingMarkers = true;
}
if (!blockFmt.headingLevel() && !mono) {
if (fontInfo.bold() != bold) {
markers += "**"_L1;
bold = fontInfo.bold();
+ if (!bold)
+ endingMarkers = true;
}
if (fontInfo.italic() != italic) {
markers += u'*';
italic = fontInfo.italic();
+ if (!italic)
+ endingMarkers = true;
}
if (fontInfo.strikeOut() != strikeOut) {
markers += "~~"_L1;
strikeOut = fontInfo.strikeOut();
+ if (!strikeOut)
+ endingMarkers = true;
}
if (fontInfo.underline() != underline) {
- // Markdown doesn't support underline, but the parser will treat a single underline
- // the same as a single asterisk, and the marked fragment will be rendered in italics.
- // That will have to do.
+ // CommonMark specifies underline as another way to get emphasis (italics):
+ // https://spec.commonmark.org/0.31.2/#example-148
+ // but md4c allows us to distinguish them; so we support underlining (in GitHub dialect).
markers += u'_';
underline = fontInfo.underline();
+ if (!underline)
+ endingMarkers = true;
}
}
}
@@ -549,7 +649,8 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
bool breakingLine = false;
while (i < fragLen) {
if (col >= ColumnLimit) {
- m_stream << qtmw_Newline << wrapIndentString;
+ m_stream << markers << qtmw_Newline << wrapIndentString;
+ markers.clear();
col = m_wrappedLineIndent;
while (i < fragLen && fragmentText[i].isSpace())
++i;
@@ -559,6 +660,13 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
int wi = nearestWordWrapIndex(fragmentText, j);
if (wi < 0) {
j = fragLen;
+ // can't break within the fragment: we need to break already _before_ it
+ if (endingMarkers) {
+ m_stream << markers;
+ markers.clear();
+ }
+ m_stream << qtmw_Newline << wrapIndentString;
+ col = m_wrappedLineIndent;
} else if (wi >= i) {
j = wi;
breakingLine = true;
@@ -582,8 +690,12 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
col += subfrag.size();
}
i = j + 1;
- }
+ } // loop over fragment characters (we know we need to break somewhere)
} else {
+ if (!m_linePrefixWritten && col == wrapIndentString.size()) {
+ m_stream << m_linePrefix;
+ col += m_linePrefix.size();
+ }
m_stream << markers << fragmentText;
col += markers.size() + fragmentText.size();
}
@@ -615,6 +727,7 @@ int QTextMarkdownWriter::writeBlock(const QTextBlock &block, bool wrap, bool ign
}
if (missedBlankCodeBlockLine)
m_stream << qtmw_Newline;
+ m_linePrefixWritten = false;
return col;
}
diff --git a/src/gui/text/qtextmarkdownwriter_p.h b/src/gui/text/qtextmarkdownwriter_p.h
index b940e37ddc..c0989b8f72 100644
--- a/src/gui/text/qtextmarkdownwriter_p.h
+++ b/src/gui/text/qtextmarkdownwriter_p.h
@@ -36,6 +36,7 @@ public:
int writeBlock(const QTextBlock &block, bool table, bool ignoreFormat, bool ignoreEmpty);
void writeFrame(const QTextFrame *frame);
+ void writeFrontMatter(const QString &fm);
private:
struct ListInfo {
@@ -43,6 +44,7 @@ private:
};
ListInfo listInfo(QTextList *list);
+ void setLinePrefixForBlockQuote(int level);
private:
QTextStream &m_stream;
@@ -53,6 +55,7 @@ private:
int m_wrappedLineIndent = 0;
int m_lastListIndent = 1;
bool m_doubleNewlineWritten = false;
+ bool m_linePrefixWritten = false;
bool m_indentedCodeBlock = false;
bool m_fencedCodeBlock = false;
};
diff --git a/src/gui/text/qtextobject.cpp b/src/gui/text/qtextobject.cpp
index f9452f43ad..6aafdc1a25 100644
--- a/src/gui/text/qtextobject.cpp
+++ b/src/gui/text/qtextobject.cpp
@@ -42,7 +42,7 @@ QT_BEGIN_NAMESPACE
objects, you will also need to reimplement QTextDocument::createObject()
which acts as a factory method for creating text objects.
- \sa QTextDocument, {Text Object Example}
+ \sa QTextDocument
*/
/*!
diff --git a/src/gui/text/qtextoption.cpp b/src/gui/text/qtextoption.cpp
index 3e5b5bc000..b6beadbe91 100644
--- a/src/gui/text/qtextoption.cpp
+++ b/src/gui/text/qtextoption.cpp
@@ -329,7 +329,7 @@ QList<QTextOption::Tab> QTextOption::tabs() const
*/
/*!
- \variable Tab::position
+ \variable QTextOption::Tab::position
Distance from the start of the paragraph.
The position of a tab is from the start of the paragraph which implies that when
the alignment of the paragraph is set to centered, the tab is interpreted to be
diff --git a/src/gui/text/unix/qfontconfigdatabase.cpp b/src/gui/text/unix/qfontconfigdatabase.cpp
index 474644b871..d607d38235 100644
--- a/src/gui/text/unix/qfontconfigdatabase.cpp
+++ b/src/gui/text/unix/qfontconfigdatabase.cpp
@@ -16,7 +16,6 @@
#include <qpa/qplatformservices.h>
#include <QtGui/private/qguiapplication_p.h>
-#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtGui/qguiapplication.h>
@@ -29,6 +28,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcFontDb)
+
static inline int mapToQtWeightForRange(int fcweight, int fcLower, int fcUpper, int qtLower, int qtUpper)
{
return qtLower + ((fcweight - fcLower) * (qtUpper - qtLower)) / (fcUpper - fcLower);
@@ -366,7 +367,10 @@ static inline bool requiresOpenType(int writingSystem)
|| writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko);
}
-static void populateFromPattern(FcPattern *pattern, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr)
+static void populateFromPattern(FcPattern *pattern,
+ QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr,
+ FT_Face face = nullptr,
+ QFontconfigDatabase *db = nullptr)
{
QString familyName;
QString familyNameLang;
@@ -489,6 +493,20 @@ static void populateFromPattern(FcPattern *pattern, QFontDatabasePrivate::Applic
}
QPlatformFontDatabase::registerFont(familyName,styleName,QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,writingSystems,fontFile);
+ if (applicationFont != nullptr && face != nullptr && db != nullptr) {
+ db->addNamedInstancesForFace(face,
+ indexValue,
+ familyName,
+ styleName,
+ weight,
+ stretch,
+ style,
+ fixedPitch,
+ writingSystems,
+ QByteArray((const char*)file_value),
+ applicationFont->data);
+ }
+
// qDebug() << familyName << (const char *)foundry_value << weight << style << &writingSystems << scalable << true << pixel_size;
for (int k = 1; FcPatternGetString(pattern, FC_FAMILY, k, &value) == FcResultMatch; ++k) {
@@ -528,6 +546,11 @@ static void populateFromPattern(FcPattern *pattern, QFontDatabasePrivate::Applic
}
+static bool isDprScaling()
+{
+ return !qFuzzyCompare(qApp->devicePixelRatio(), 1.0);
+}
+
QFontconfigDatabase::~QFontconfigDatabase()
{
FcConfigDestroy(FcConfigGetCurrent());
@@ -556,6 +579,12 @@ void QFontconfigDatabase::populateFontDatabase()
FcObjectSetAdd(os, *p);
++p;
}
+
+#ifdef FC_VARIABLE
+ /* Support the named instance of Variable Fonts. */
+ FcPatternAddBool(pattern, FC_VARIABLE, FcFalse);
+#endif
+
fonts = FcFontList(nullptr, pattern, os);
FcObjectSetDestroy(os);
FcPatternDestroy(pattern);
@@ -613,7 +642,7 @@ QFontEngineMulti *QFontconfigDatabase::fontEngineMulti(QFontEngine *fontEngine,
}
namespace {
-QFontEngine::HintStyle defaultHintStyleFromMatch(QFont::HintingPreference hintingPreference, FcPattern *match, bool useXftConf)
+QFontEngine::HintStyle defaultHintStyleFromMatch(QFont::HintingPreference hintingPreference, FcPattern *match, bool preferXftConf)
{
switch (hintingPreference) {
case QFont::PreferNoHinting:
@@ -626,9 +655,16 @@ QFontEngine::HintStyle defaultHintStyleFromMatch(QFont::HintingPreference hintin
break;
}
- if (QHighDpiScaling::isActive())
+ if (isDprScaling())
return QFontEngine::HintNone;
+ void *hintStyleResource =
+ QGuiApplication::platformNativeInterface()->nativeResourceForScreen("hintstyle",
+ QGuiApplication::primaryScreen());
+ int xftHintStyle = int(reinterpret_cast<qintptr>(hintStyleResource));
+ if (preferXftConf && xftHintStyle > 0)
+ return QFontEngine::HintStyle(xftHintStyle - 1);
+
int hint_style = 0;
if (FcPatternGetInteger (match, FC_HINT_STYLE, 0, &hint_style) == FcResultMatch) {
switch (hint_style) {
@@ -645,21 +681,21 @@ QFontEngine::HintStyle defaultHintStyleFromMatch(QFont::HintingPreference hintin
break;
}
}
-
- if (useXftConf) {
- void *hintStyleResource =
- QGuiApplication::platformNativeInterface()->nativeResourceForScreen("hintstyle",
- QGuiApplication::primaryScreen());
- int hintStyle = int(reinterpret_cast<qintptr>(hintStyleResource));
- if (hintStyle > 0)
- return QFontEngine::HintStyle(hintStyle - 1);
- }
+ if (xftHintStyle > 0)
+ return QFontEngine::HintStyle(xftHintStyle - 1);
return QFontEngine::HintFull;
}
-QFontEngine::SubpixelAntialiasingType subpixelTypeFromMatch(FcPattern *match, bool useXftConf)
+QFontEngine::SubpixelAntialiasingType subpixelTypeFromMatch(FcPattern *match, bool preferXftConf)
{
+ void *subpixelTypeResource =
+ QGuiApplication::platformNativeInterface()->nativeResourceForScreen("subpixeltype",
+ QGuiApplication::primaryScreen());
+ int xftSubpixelType = int(reinterpret_cast<qintptr>(subpixelTypeResource));
+ if (preferXftConf && xftSubpixelType > 0)
+ return QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
+
int subpixel = FC_RGBA_UNKNOWN;
if (FcPatternGetInteger(match, FC_RGBA, 0, &subpixel) == FcResultMatch) {
switch (subpixel) {
@@ -680,14 +716,8 @@ QFontEngine::SubpixelAntialiasingType subpixelTypeFromMatch(FcPattern *match, bo
}
}
- if (useXftConf) {
- void *subpixelTypeResource =
- QGuiApplication::platformNativeInterface()->nativeResourceForScreen("subpixeltype",
- QGuiApplication::primaryScreen());
- int subpixelType = int(reinterpret_cast<qintptr>(subpixelTypeResource));
- if (subpixelType > 0)
- return QFontEngine::SubpixelAntialiasingType(subpixelType - 1);
- }
+ if (xftSubpixelType > 0)
+ return QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
return QFontEngine::Subpixel_None;
}
@@ -702,6 +732,8 @@ QFontEngine *QFontconfigDatabase::fontEngine(const QFontDef &f, void *usrPtr)
QFontEngine::FaceId fid;
fid.filename = QFile::encodeName(fontfile->fileName);
fid.index = fontfile->indexValue;
+ fid.instanceIndex = fontfile->instanceIndex;
+ fid.variableAxes = f.variableAxisValues;
// FIXME: Unify with logic in QFontEngineFT::create()
QFontEngineFT *engine = new QFontEngineFT(f);
@@ -803,26 +835,28 @@ QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont
return fallbackFamilies;
}
-static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count)
+static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count, FT_Face *face)
{
#if FC_VERSION < 20402
Q_UNUSED(data);
+ *face = nullptr;
return FcFreeTypeQuery(file, id, blanks, count);
#else
- if (data.isEmpty())
+ if (data.isEmpty()) {
+ *face = nullptr;
return FcFreeTypeQuery(file, id, blanks, count);
+ }
FT_Library lib = qt_getFreetype();
FcPattern *pattern = nullptr;
- FT_Face face;
- if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, &face)) {
- *count = face->num_faces;
-
- pattern = FcFreeTypeQueryFace(face, file, id, blanks);
+ if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, face)) {
+ *count = (*face)->num_faces;
- FT_Done_Face(face);
+ pattern = FcFreeTypeQueryFace(*face, file, id, blanks);
+ } else {
+ *face = nullptr;
}
return pattern;
@@ -850,8 +884,9 @@ QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData,
FcPattern *pattern;
do {
+ FT_Face face;
pattern = queryFont((const FcChar8 *)QFile::encodeName(fileName).constData(),
- fontData, id, blanks, &count);
+ fontData, id, blanks, &count, &face);
if (!pattern)
return families;
@@ -860,7 +895,10 @@ QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData,
QString family = QString::fromUtf8(reinterpret_cast<const char *>(fam));
families << family;
}
- populateFromPattern(pattern, applicationFont);
+ populateFromPattern(pattern, applicationFont, face, this);
+
+ if (face)
+ FT_Done_Face(face);
FcFontSetAdd(set, pattern);
@@ -925,28 +963,20 @@ QFont QFontconfigDatabase::defaultFont() const
void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef &fontDef) const
{
bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias);
- bool forcedAntialiasSetting = !antialias || QHighDpiScaling::isActive();
+ bool forcedAntialiasSetting = !antialias || isDprScaling();
const QPlatformServices *services = QGuiApplicationPrivate::platformIntegration()->services();
- bool useXftConf = false;
+ bool preferXftConf = false;
if (services) {
const QList<QByteArray> desktopEnv = services->desktopEnvironment().split(':');
- useXftConf = desktopEnv.contains("GNOME") || desktopEnv.contains("UNITY") || desktopEnv.contains("XFCE");
- }
-
- if (useXftConf && !forcedAntialiasSetting) {
- void *antialiasResource =
- QGuiApplication::platformNativeInterface()->nativeResourceForScreen("antialiasingEnabled",
- QGuiApplication::primaryScreen());
- int antialiasingEnabled = int(reinterpret_cast<qintptr>(antialiasResource));
- if (antialiasingEnabled > 0)
- antialias = antialiasingEnabled - 1;
+ preferXftConf = !(desktopEnv.contains("KDE") || desktopEnv.contains("LXQT") || desktopEnv.contains("UKUI"));
}
QFontEngine::GlyphFormat format;
// try and get the pattern
FcPattern *pattern = FcPatternCreate();
+ FcPattern *match = nullptr;
FcValue value;
value.type = FcTypeString;
@@ -965,7 +995,7 @@ void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef
FcPatternAdd(pattern,FC_INDEX,value,true);
}
- if (fontDef.pixelSize > 0.1)
+ if (!qFuzzyIsNull(fontDef.pixelSize))
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontDef.pixelSize);
FcResult result;
@@ -973,9 +1003,68 @@ void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
- FcPattern *match = FcFontMatch(nullptr, pattern, &result);
+#ifdef FC_VARIABLE
+ if (!fid.filename.isEmpty()) {
+ // FC_INDEX is ignored during processing in FcFontMatch.
+ // So iterate FcPatterns directly and find it out.
+ FcFontSet *fcsets[2], *fcfs;
+
+ fcsets[0] = FcConfigGetFonts(nullptr, FcSetSystem);
+ fcsets[1] = FcConfigGetFonts(nullptr, FcSetApplication);
+ for (int nset = 0; nset < 2; nset++) {
+ fcfs = fcsets[nset];
+ if (fcfs == nullptr)
+ continue;
+ for (int fnum = 0; fnum < fcfs->nfont; fnum++) {
+ FcPattern *fcpat = fcfs->fonts[fnum];
+ FcChar8 *fcfile;
+ FcBool variable;
+ double fcpixelsize;
+ int fcindex;
+
+ // Skip the variable font itself, only to use the named instances and normal fonts here
+ if (FcPatternGetBool(fcpat, FC_VARIABLE, 0, &variable) == FcResultMatch &&
+ variable == FcTrue)
+ continue;
+
+ if (!qFuzzyIsNull(fontDef.pixelSize)) {
+ if (FcPatternGetDouble(fcpat, FC_PIXEL_SIZE, 0, &fcpixelsize) == FcResultMatch &&
+ fontDef.pixelSize != fcpixelsize)
+ continue;
+ }
+
+ if (FcPatternGetString(fcpat, FC_FILE, 0, &fcfile) == FcResultMatch &&
+ FcPatternGetInteger(fcpat, FC_INDEX, 0, &fcindex) == FcResultMatch) {
+ QByteArray f = QByteArray::fromRawData((const char *)fcfile,
+ qstrlen((const char *)fcfile));
+ if (f == fid.filename && fcindex == fid.index) {
+ // We found it.
+ match = FcFontRenderPrepare(nullptr, pattern, fcpat);
+ goto bail;
+ }
+ }
+ }
+ }
+ }
+bail:
+#endif
+
+ if (!match)
+ match = FcFontMatch(nullptr, pattern, &result);
+
+ int xftAntialias = 0;
+ if (!forcedAntialiasSetting) {
+ void *antialiasResource =
+ QGuiApplication::platformNativeInterface()->nativeResourceForScreen("antialiasingEnabled",
+ QGuiApplication::primaryScreen());
+ xftAntialias = int(reinterpret_cast<qintptr>(antialiasResource));
+ if ((preferXftConf || !match) && xftAntialias > 0) {
+ antialias = xftAntialias - 1;
+ forcedAntialiasSetting = true;
+ }
+ }
if (match) {
- engine->setDefaultHintStyle(defaultHintStyleFromMatch((QFont::HintingPreference)fontDef.hintingPreference, match, useXftConf));
+ engine->setDefaultHintStyle(defaultHintStyleFromMatch((QFont::HintingPreference)fontDef.hintingPreference, match, preferXftConf));
FcBool fc_autohint;
if (FcPatternGetBool(match, FC_AUTOHINT,0, &fc_autohint) == FcResultMatch)
@@ -996,18 +1085,37 @@ void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef
if (antialias) {
QFontEngine::SubpixelAntialiasingType subpixelType = QFontEngine::Subpixel_None;
if (!(fontDef.styleStrategy & QFont::NoSubpixelAntialias))
- subpixelType = subpixelTypeFromMatch(match, useXftConf);
+ subpixelType = subpixelTypeFromMatch(match, preferXftConf);
engine->subpixelType = subpixelType;
-
- format = (subpixelType == QFontEngine::Subpixel_None)
- ? QFontEngine::Format_A8
- : QFontEngine::Format_A32;
- } else
- format = QFontEngine::Format_Mono;
+ }
FcPatternDestroy(match);
- } else
- format = antialias ? QFontEngine::Format_A8 : QFontEngine::Format_Mono;
+ } else {
+ void *hintStyleResource =
+ QGuiApplication::platformNativeInterface()->nativeResourceForScreen("hintstyle",
+ QGuiApplication::primaryScreen());
+ int xftHintStyle = int(reinterpret_cast<qintptr>(hintStyleResource));
+ if (xftHintStyle > 0)
+ engine->setDefaultHintStyle(QFontEngine::HintStyle(xftHintStyle - 1));
+ if (antialias) {
+ engine->subpixelType = QFontEngine::Subpixel_None;
+ if (!(fontDef.styleStrategy & QFont::NoSubpixelAntialias)) {
+ void *subpixelTypeResource =
+ QGuiApplication::platformNativeInterface()->nativeResourceForScreen("subpixeltype",
+ QGuiApplication::primaryScreen());
+ int xftSubpixelType = int(reinterpret_cast<qintptr>(subpixelTypeResource));
+ if (xftSubpixelType > 1)
+ engine->subpixelType = QFontEngine::SubpixelAntialiasingType(xftSubpixelType - 1);
+ }
+ }
+ }
+ if (antialias) {
+ format = (engine->subpixelType == QFontEngine::Subpixel_None)
+ ? QFontEngine::Format_A8
+ : QFontEngine::Format_A32;
+ } else {
+ format = QFontEngine::Format_Mono;
+ }
FcPatternDestroy(pattern);
@@ -1016,4 +1124,13 @@ void QFontconfigDatabase::setupFontEngine(QFontEngineFT *engine, const QFontDef
engine->glyphFormat = format;
}
+bool QFontconfigDatabase::supportsVariableApplicationFonts() const
+{
+#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
+ return true;
+#else
+ return false;
+#endif
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/unix/qfontconfigdatabase_p.h b/src/gui/text/unix/qfontconfigdatabase_p.h
index cf15306e40..dd7a70a375 100644
--- a/src/gui/text/unix/qfontconfigdatabase_p.h
+++ b/src/gui/text/unix/qfontconfigdatabase_p.h
@@ -28,6 +28,7 @@ public:
~QFontconfigDatabase() override;
void populateFontDatabase() override;
void invalidate() override;
+ bool supportsVariableApplicationFonts() const override;
QFontEngineMulti *fontEngineMulti(QFontEngine *fontEngine, QChar::Script script) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
diff --git a/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp b/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
index 6b4933cca7..2e15fbb1ac 100644
--- a/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
+++ b/src/gui/text/windows/qwindowsdirectwritefontdatabase.cpp
@@ -18,6 +18,32 @@ QT_BEGIN_NAMESPACE
// Defined in gui/text/qfontdatabase.cpp
Q_GUI_EXPORT QFontDatabase::WritingSystem qt_writing_system_for_script(int script);
+template<typename T>
+struct DirectWriteScope {
+ DirectWriteScope(T *res = nullptr) : m_res(res) {}
+ ~DirectWriteScope() {
+ if (m_res != nullptr)
+ m_res->Release();
+ }
+
+ T **operator&()
+ {
+ return &m_res;
+ }
+
+ T *operator->()
+ {
+ return m_res;
+ }
+
+ T *operator*() {
+ return m_res;
+ }
+
+private:
+ T *m_res;
+};
+
QWindowsDirectWriteFontDatabase::QWindowsDirectWriteFontDatabase()
{
qCDebug(lcQpaFonts) << "Creating DirectWrite database";
@@ -80,6 +106,12 @@ static QFont::Style fromDirectWriteStyle(DWRITE_FONT_STYLE style)
void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
{
auto it = m_populatedFonts.find(familyName);
+ if (it == m_populatedFonts.end() && m_populatedBitmapFonts.contains(familyName)) {
+ qCDebug(lcQpaFonts) << "Populating bitmap font" << familyName;
+ QWindowsFontDatabase::populateFamily(familyName);
+ return;
+ }
+
IDWriteFontFamily *fontFamily = it != m_populatedFonts.end() ? it.value() : nullptr;
if (fontFamily == nullptr) {
qCWarning(lcQpaFonts) << "Cannot find" << familyName << "in list of fonts";
@@ -98,7 +130,7 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
const bool antialias = false;
const int size = SMOOTH_SCALABLE;
- IDWriteFontList *matchingFonts;
+ DirectWriteScope<IDWriteFontList> matchingFonts;
if (SUCCEEDED(fontFamily->GetMatchingFonts(DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
@@ -106,7 +138,7 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
for (uint j = 0; j < matchingFonts->GetFontCount(); ++j) {
IDWriteFont *font;
if (SUCCEEDED(matchingFonts->GetFont(j, &font))) {
- IDWriteFont1 *font1 = nullptr;
+ DirectWriteScope<IDWriteFont1> font1;
if (!SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont1),
reinterpret_cast<void **>(&font1)))) {
qCWarning(lcQpaFonts) << "COM object does not support IDWriteFont1";
@@ -116,27 +148,23 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
QString defaultLocaleFamilyName;
QString englishLocaleFamilyName;
- IDWriteFontFamily *fontFamily2;
+ DirectWriteScope<IDWriteFontFamily> fontFamily2;
if (SUCCEEDED(font1->GetFontFamily(&fontFamily2))) {
- IDWriteLocalizedStrings *names;
+ DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(fontFamily2->GetFamilyNames(&names))) {
- defaultLocaleFamilyName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
- englishLocaleFamilyName = localeString(names, englishLocale);
-
- names->Release();
+ defaultLocaleFamilyName = hasDefaultLocale ? localeString(*names, defaultLocale) : QString();
+ englishLocaleFamilyName = localeString(*names, englishLocale);
}
-
- fontFamily2->Release();
}
if (defaultLocaleFamilyName.isEmpty() && englishLocaleFamilyName.isEmpty())
englishLocaleFamilyName = familyName;
{
- IDWriteLocalizedStrings *names;
+ DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(font1->GetFaceNames(&names))) {
- QString defaultLocaleStyleName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
- QString englishLocaleStyleName = localeString(names, englishLocale);
+ QString defaultLocaleStyleName = hasDefaultLocale ? localeString(*names, defaultLocale) : QString();
+ QString englishLocaleStyleName = localeString(*names, englishLocale);
QFont::Stretch stretch = fromDirectWriteStretch(font1->GetStretch());
QFont::Style style = fromDirectWriteStyle(font1->GetStyle());
@@ -145,77 +173,233 @@ void QWindowsDirectWriteFontDatabase::populateFamily(const QString &familyName)
qCDebug(lcQpaFonts) << "Family" << familyName << "has english variant" << englishLocaleStyleName << ", in default locale:" << defaultLocaleStyleName << stretch << style << weight << fixed;
- IDWriteFontFace *face = nullptr;
+ DirectWriteScope<IDWriteFontFace> face;
if (SUCCEEDED(font->CreateFontFace(&face))) {
- QSupportedWritingSystems writingSystems;
-
- const void *tableData = nullptr;
- UINT32 tableSize;
- void *tableContext = nullptr;
- BOOL exists;
- HRESULT hr = face->TryGetFontTable(qbswap<quint32>(MAKE_TAG('O','S','/','2')),
- &tableData,
- &tableSize,
- &tableContext,
- &exists);
- if (SUCCEEDED(hr) && exists) {
- writingSystems = QPlatformFontDatabase::writingSystemsFromOS2Table(reinterpret_cast<const char *>(tableData), tableSize);
- } else { // Fall back to checking first character of each Unicode range in font (may include too many writing systems)
- quint32 rangeCount;
- hr = font1->GetUnicodeRanges(0, nullptr, &rangeCount);
-
- if (rangeCount > 0) {
- QVarLengthArray<DWRITE_UNICODE_RANGE, QChar::ScriptCount> ranges(rangeCount);
-
- hr = font1->GetUnicodeRanges(rangeCount, ranges.data(), &rangeCount);
- if (SUCCEEDED(hr)) {
- for (uint i = 0; i < rangeCount; ++i) {
- QChar::Script script = QChar::script(ranges.at(i).first);
-
- QFontDatabase::WritingSystem writingSystem = qt_writing_system_for_script(script);
-
- if (writingSystem > QFontDatabase::Any && writingSystem < QFontDatabase::WritingSystemsCount)
- writingSystems.setSupported(writingSystem);
- }
- } else {
- const QString errorString = qt_error_string(int(hr));
- qCWarning(lcQpaFonts) << "Failed to get unicode ranges for font" << englishLocaleFamilyName << englishLocaleStyleName << ":" << errorString;
- }
- }
- }
+ QSupportedWritingSystems writingSystems = supportedWritingSystems(*face);
if (!englishLocaleStyleName.isEmpty() || defaultLocaleStyleName.isEmpty()) {
qCDebug(lcQpaFonts) << "Font" << englishLocaleFamilyName << englishLocaleStyleName << "supports writing systems:" << writingSystems;
- QPlatformFontDatabase::registerFont(englishLocaleFamilyName, englishLocaleStyleName, QString(), weight, style, stretch, antialias, scalable, size, fixed, writingSystems, face);
- face->AddRef();
+ QPlatformFontDatabase::registerFont(englishLocaleFamilyName,
+ englishLocaleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(*face, englishLocaleFamilyName));
}
if (!defaultLocaleFamilyName.isEmpty() && defaultLocaleFamilyName != englishLocaleFamilyName) {
- QPlatformFontDatabase::registerFont(defaultLocaleFamilyName, defaultLocaleStyleName, QString(), weight, style, stretch, antialias, scalable, size, fixed, writingSystems, face);
- face->AddRef();
+ QPlatformFontDatabase::registerFont(defaultLocaleFamilyName,
+ defaultLocaleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(*face, defaultLocaleFamilyName));
}
-
- face->Release();
}
-
- names->Release();
}
}
+ }
+ }
+ }
+}
- font1->Release();
- font->Release();
+QSupportedWritingSystems QWindowsDirectWriteFontDatabase::supportedWritingSystems(IDWriteFontFace *face) const
+{
+ QSupportedWritingSystems writingSystems;
+ writingSystems.setSupported(QFontDatabase::Any);
+
+ DirectWriteScope<IDWriteFontFace1> face1;
+ if (SUCCEEDED(face->QueryInterface(__uuidof(IDWriteFontFace1),
+ reinterpret_cast<void **>(&face1)))) {
+ const void *tableData = nullptr;
+ UINT32 tableSize;
+ void *tableContext = nullptr;
+ BOOL exists;
+ HRESULT hr = face->TryGetFontTable(qFromBigEndian(QFont::Tag("OS/2").value()),
+ &tableData,
+ &tableSize,
+ &tableContext,
+ &exists);
+ if (SUCCEEDED(hr) && exists) {
+ writingSystems = QPlatformFontDatabase::writingSystemsFromOS2Table(reinterpret_cast<const char *>(tableData), tableSize);
+ } else { // Fall back to checking first character of each Unicode range in font (may include too many writing systems)
+ quint32 rangeCount;
+ hr = face1->GetUnicodeRanges(0, nullptr, &rangeCount);
+
+ if (rangeCount > 0) {
+ QVarLengthArray<DWRITE_UNICODE_RANGE, QChar::ScriptCount> ranges(rangeCount);
+
+ hr = face1->GetUnicodeRanges(rangeCount, ranges.data(), &rangeCount);
+ if (SUCCEEDED(hr)) {
+ for (uint i = 0; i < rangeCount; ++i) {
+ QChar::Script script = QChar::script(ranges.at(i).first);
+
+ QFontDatabase::WritingSystem writingSystem = qt_writing_system_for_script(script);
+
+ if (writingSystem > QFontDatabase::Any && writingSystem < QFontDatabase::WritingSystemsCount)
+ writingSystems.setSupported(writingSystem);
+ }
+ } else {
+ const QString errorString = qt_error_string(int(hr));
+ qCWarning(lcQpaFonts) << "Failed to get unicode ranges for font:" << errorString;
+ }
}
}
+ }
+
+ return writingSystems;
+}
+
+bool QWindowsDirectWriteFontDatabase::populateFamilyAliases(const QString &missingFamily)
+{
+ // If the font has not been populated, it is possible this is a legacy font family supported
+ // by GDI. We make an attempt at loading it via GDI and then add this face directly to the
+ // database.
+ if (!missingFamily.isEmpty()
+ && missingFamily.size() < LF_FACESIZE
+ && !m_populatedFonts.contains(missingFamily)
+ && !m_populatedBitmapFonts.contains(missingFamily)) {
+ qCDebug(lcQpaFonts) << "Loading unpopulated" << missingFamily << ". Trying GDI.";
+
+ LOGFONT lf;
+ memset(&lf, 0, sizeof(LOGFONT));
+ memcpy(lf.lfFaceName, missingFamily.utf16(), missingFamily.size() * sizeof(wchar_t));
+
+ HFONT hfont = CreateFontIndirect(&lf);
+ if (hfont) {
+ HDC dummy = GetDC(0);
+ HGDIOBJ oldFont = SelectObject(dummy, hfont);
+
+ DirectWriteScope<IDWriteFontFace> directWriteFontFace;
+ if (SUCCEEDED(data()->directWriteGdiInterop->CreateFontFaceFromHdc(dummy, &directWriteFontFace))) {
+ DirectWriteScope<IDWriteFontCollection> fontCollection;
+ if (SUCCEEDED(data()->directWriteFactory->GetSystemFontCollection(&fontCollection))) {
+ DirectWriteScope<IDWriteFont> font;
+ if (SUCCEEDED(fontCollection->GetFontFromFontFace(*directWriteFontFace, &font))) {
+
+ DirectWriteScope<IDWriteFont1> font1;
+ if (SUCCEEDED(font->QueryInterface(__uuidof(IDWriteFont1),
+ reinterpret_cast<void **>(&font1)))) {
+ DirectWriteScope<IDWriteLocalizedStrings> names;
+ if (SUCCEEDED(font1->GetFaceNames(&names))) {
+ wchar_t englishLocale[] = L"en-us";
+ QString englishLocaleStyleName = localeString(*names, englishLocale);
+
+ QFont::Stretch stretch = fromDirectWriteStretch(font1->GetStretch());
+ QFont::Style style = fromDirectWriteStyle(font1->GetStyle());
+ QFont::Weight weight = fromDirectWriteWeight(font1->GetWeight());
+ bool fixed = font1->IsMonospacedFont();
+
+ QSupportedWritingSystems writingSystems = supportedWritingSystems(*directWriteFontFace);
+
+ qCDebug(lcQpaFonts) << "Registering legacy font family" << missingFamily;
+ QPlatformFontDatabase::registerFont(missingFamily,
+ englishLocaleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ false,
+ true,
+ 0xffff,
+ fixed,
+ writingSystems,
+ new FontHandle(*directWriteFontFace, missingFamily));
+
+ SelectObject(dummy, oldFont);
+ DeleteObject(hfont);
+
+ return true;
+ }
+ }
+ }
+ }
+ }
- matchingFonts->Release();
+ SelectObject(dummy, oldFont);
+ DeleteObject(hfont);
+ }
}
+
+ // Skip over implementation in QWindowsFontDatabase
+ return QWindowsFontDatabaseBase::populateFamilyAliases(missingFamily);
+}
+
+QFontEngine *QWindowsDirectWriteFontDatabase::fontEngine(const QByteArray &fontData,
+ qreal pixelSize,
+ QFont::HintingPreference hintingPreference)
+{
+ // Skip over implementation in QWindowsFontDatabase
+ return QWindowsFontDatabaseBase::fontEngine(fontData, pixelSize, hintingPreference);
}
QFontEngine *QWindowsDirectWriteFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
{
- IDWriteFontFace *face = reinterpret_cast<IDWriteFontFace *>(handle);
- Q_ASSERT(face != nullptr);
+ const FontHandle *fontHandle = static_cast<const FontHandle *>(handle);
+ IDWriteFontFace *face = fontHandle->fontFace;
+ if (face == nullptr) {
+ qCDebug(lcQpaFonts) << "Falling back to GDI";
+ return QWindowsFontDatabase::fontEngine(fontDef, handle);
+ }
+
+ DWRITE_FONT_SIMULATIONS simulations = DWRITE_FONT_SIMULATIONS_NONE;
+ if (fontDef.weight >= QFont::DemiBold || fontDef.style != QFont::StyleNormal) {
+ DirectWriteScope<IDWriteFontFace3> face3;
+ if (SUCCEEDED(face->QueryInterface(__uuidof(IDWriteFontFace3),
+ reinterpret_cast<void **>(&face3)))) {
+ if (fontDef.weight >= QFont::DemiBold && face3->GetWeight() < DWRITE_FONT_WEIGHT_DEMI_BOLD)
+ simulations |= DWRITE_FONT_SIMULATIONS_BOLD;
+
+ if (fontDef.style != QFont::StyleNormal && face3->GetStyle() == DWRITE_FONT_STYLE_NORMAL)
+ simulations |= DWRITE_FONT_SIMULATIONS_OBLIQUE;
+ }
+ }
+
+ DirectWriteScope<IDWriteFontFace5> newFace;
+ if (!fontDef.variableAxisValues.isEmpty() || simulations != DWRITE_FONT_SIMULATIONS_NONE) {
+ DirectWriteScope<IDWriteFontFace5> face5;
+ if (SUCCEEDED(face->QueryInterface(__uuidof(IDWriteFontFace5),
+ reinterpret_cast<void **>(&face5)))) {
+ DirectWriteScope<IDWriteFontResource> font;
+ if (SUCCEEDED(face5->GetFontResource(&font))) {
+ UINT32 fontAxisCount = font->GetFontAxisCount();
+ QVarLengthArray<DWRITE_FONT_AXIS_VALUE, 8> fontAxisValues(fontAxisCount);
+
+ if (!fontDef.variableAxisValues.isEmpty()) {
+ if (SUCCEEDED(face5->GetFontAxisValues(fontAxisValues.data(), fontAxisCount))) {
+ for (UINT32 i = 0; i < fontAxisCount; ++i) {
+ if (auto maybeTag = QFont::Tag::fromValue(qToBigEndian<UINT32>(fontAxisValues[i].axisTag))) {
+ if (fontDef.variableAxisValues.contains(*maybeTag))
+ fontAxisValues[i].value = fontDef.variableAxisValues.value(*maybeTag);
+ }
+ }
+ }
+ }
+
+ if (SUCCEEDED(font->CreateFontFace(simulations,
+ !fontDef.variableAxisValues.isEmpty() ? fontAxisValues.data() : nullptr,
+ !fontDef.variableAxisValues.isEmpty() ? fontAxisCount : 0,
+ &newFace))) {
+ face = *newFace;
+ } else {
+ qCWarning(lcQpaFonts) << "DirectWrite: Can't create font face for variable axis values";
+ }
+ }
+ }
+ }
QWindowsFontEngineDirectWrite *fontEngine = new QWindowsFontEngineDirectWrite(face, fontDef.pixelSize, data());
fontEngine->initFontInfo(fontDef, defaultVerticalDPI());
@@ -249,111 +433,255 @@ QStringList QWindowsDirectWriteFontDatabase::addApplicationFont(const QByteArray
loadedData = file.readAll();
}
- IDWriteFontFace *face = createDirectWriteFace(loadedData);
- if (face == nullptr) {
+ QList<IDWriteFontFace *> faces = createDirectWriteFaces(loadedData);
+ if (faces.isEmpty()) {
qCWarning(lcQpaFonts) << "Failed to create DirectWrite face from font data. Font may be unsupported.";
return QStringList();
}
- wchar_t defaultLocale[LOCALE_NAME_MAX_LENGTH];
- bool hasDefaultLocale = GetUserDefaultLocaleName(defaultLocale, LOCALE_NAME_MAX_LENGTH) != 0;
- wchar_t englishLocale[] = L"en-us";
+ QSet<QString> ret;
+ for (int i = 0; i < faces.size(); ++i) {
+ IDWriteFontFace *face = faces.at(i);
+ wchar_t defaultLocale[LOCALE_NAME_MAX_LENGTH];
+ bool hasDefaultLocale = GetUserDefaultLocaleName(defaultLocale, LOCALE_NAME_MAX_LENGTH) != 0;
+ wchar_t englishLocale[] = L"en-us";
+
+ static const int SMOOTH_SCALABLE = 0xffff;
+ const bool scalable = true;
+ const bool antialias = false;
+ const int size = SMOOTH_SCALABLE;
+
+ QSupportedWritingSystems writingSystems = supportedWritingSystems(face);
+ DirectWriteScope<IDWriteFontFace3> face3;
+ if (SUCCEEDED(face->QueryInterface(__uuidof(IDWriteFontFace3),
+ reinterpret_cast<void **>(&face3)))) {
+ QString defaultLocaleFamilyName;
+ QString englishLocaleFamilyName;
+
+ IDWriteLocalizedStrings *names = nullptr;
+ if (SUCCEEDED(face3->GetFamilyNames(&names))) {
+ defaultLocaleFamilyName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleFamilyName = localeString(names, englishLocale);
+
+ names->Release();
+ }
- static const int SMOOTH_SCALABLE = 0xffff;
- const QString foundryName; // No such concept.
- const bool scalable = true;
- const bool antialias = false;
- const int size = SMOOTH_SCALABLE;
+ QString defaultLocaleStyleName;
+ QString englishLocaleStyleName;
+ if (SUCCEEDED(face3->GetFaceNames(&names))) {
+ defaultLocaleStyleName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleStyleName = localeString(names, englishLocale);
- QSupportedWritingSystems writingSystems;
- writingSystems.setSupported(QFontDatabase::Any);
- writingSystems.setSupported(QFontDatabase::Latin);
+ names->Release();
+ }
- QStringList ret;
- IDWriteFontFace3 *face3 = nullptr;
- if (SUCCEEDED(face->QueryInterface(__uuidof(IDWriteFontFace3),
- reinterpret_cast<void **>(&face3)))) {
- QString defaultLocaleFamilyName;
- QString englishLocaleFamilyName;
+ BOOL ok;
+ QString defaultLocaleGdiCompatibleFamilyName;
+ QString englishLocaleGdiCompatibleFamilyName;
+ if (SUCCEEDED(face3->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, &names, &ok)) && ok) {
+ defaultLocaleGdiCompatibleFamilyName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleGdiCompatibleFamilyName = localeString(names, englishLocale);
- IDWriteLocalizedStrings *names;
- if (SUCCEEDED(face3->GetFamilyNames(&names))) {
- defaultLocaleFamilyName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
- englishLocaleFamilyName = localeString(names, englishLocale);
+ names->Release();
+ }
- names->Release();
- }
+ QString defaultLocaleGdiCompatibleStyleName;
+ QString englishLocaleGdiCompatibleStyleName;
+ if (SUCCEEDED(face3->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES, &names, &ok)) && ok) {
+ defaultLocaleGdiCompatibleStyleName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleGdiCompatibleStyleName = localeString(names, englishLocale);
- QString defaultLocaleStyleName;
- QString englishLocaleStyleName;
- if (SUCCEEDED(face3->GetFaceNames(&names))) {
- defaultLocaleStyleName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
- englishLocaleStyleName = localeString(names, englishLocale);
+ names->Release();
+ }
- names->Release();
- }
+ QString defaultLocaleTypographicFamilyName;
+ QString englishLocaleTypographicFamilyName;
+ if (SUCCEEDED(face3->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_FAMILY_NAMES, &names, &ok)) && ok) {
+ defaultLocaleTypographicFamilyName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleTypographicFamilyName = localeString(names, englishLocale);
- QFont::Stretch stretch = fromDirectWriteStretch(face3->GetStretch());
- QFont::Style style = fromDirectWriteStyle(face3->GetStyle());
- QFont::Weight weight = fromDirectWriteWeight(face3->GetWeight());
- bool fixed = face3->IsMonospacedFont();
-
- qCDebug(lcQpaFonts) << "\tFont names:" << englishLocaleFamilyName << ", " << defaultLocaleFamilyName
- << ", style names:" << englishLocaleStyleName << ", " << defaultLocaleStyleName
- << ", stretch:" << stretch
- << ", style:" << style
- << ", weight:" << weight
- << ", fixed:" << fixed;
-
- if (!englishLocaleFamilyName.isEmpty()) {
- if (applicationFont != nullptr) {
- QFontDatabasePrivate::ApplicationFont::Properties properties;
- properties.style = style;
- properties.weight = weight;
- properties.familyName = englishLocaleFamilyName;
- properties.styleName = englishLocaleStyleName;
- applicationFont->properties.append(properties);
+ names->Release();
}
- ret.append(englishLocaleFamilyName);
- QPlatformFontDatabase::registerFont(englishLocaleFamilyName, englishLocaleStyleName, QString(), weight, style, stretch, antialias, scalable, size, fixed, writingSystems, face);
- face->AddRef();
- }
+ QString defaultLocaleTypographicStyleName;
+ QString englishLocaleTypographicStyleName;
+ if (SUCCEEDED(face3->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_SUBFAMILY_NAMES, &names, &ok)) && ok) {
+ defaultLocaleTypographicStyleName = hasDefaultLocale ? localeString(names, defaultLocale) : QString();
+ englishLocaleTypographicStyleName = localeString(names, englishLocale);
- if (!defaultLocaleFamilyName.isEmpty() && defaultLocaleFamilyName != englishLocaleFamilyName) {
- if (applicationFont != nullptr) {
- QFontDatabasePrivate::ApplicationFont::Properties properties;
- properties.style = style;
- properties.weight = weight;
- properties.familyName = englishLocaleFamilyName;
- properties.styleName = englishLocaleStyleName;
- applicationFont->properties.append(properties);
+ names->Release();
}
- ret.append(defaultLocaleFamilyName);
- QPlatformFontDatabase::registerFont(defaultLocaleFamilyName, defaultLocaleStyleName, QString(), weight, style, stretch, antialias, scalable, size, fixed, writingSystems, face);
- face->AddRef();
- }
+ QFont::Stretch stretch = fromDirectWriteStretch(face3->GetStretch());
+ QFont::Style style = fromDirectWriteStyle(face3->GetStyle());
+ QFont::Weight weight = fromDirectWriteWeight(face3->GetWeight());
+ bool fixed = face3->IsMonospacedFont();
+
+ qCDebug(lcQpaFonts) << "\tFont names:" << englishLocaleFamilyName << ", " << defaultLocaleFamilyName
+ << ", style names:" << englishLocaleStyleName << ", " << defaultLocaleStyleName
+ << ", stretch:" << stretch
+ << ", style:" << style
+ << ", weight:" << weight
+ << ", fixed:" << fixed;
+
+ if (!englishLocaleFamilyName.isEmpty()) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = englishLocaleFamilyName;
+ properties.styleName = englishLocaleStyleName;
+ applicationFont->properties.append(properties);
+ }
- face3->Release();
- } else {
- qCWarning(lcQpaFonts) << "Unable to query IDWriteFontFace3 interface from font face.";
- }
+ ret.insert(englishLocaleFamilyName);
+ QPlatformFontDatabase::registerFont(englishLocaleFamilyName,
+ englishLocaleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, englishLocaleFamilyName));
+ }
- face->Release();
+ if (!defaultLocaleFamilyName.isEmpty() && !ret.contains(defaultLocaleFamilyName)) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = englishLocaleFamilyName;
+ properties.styleName = englishLocaleStyleName;
+ applicationFont->properties.append(properties);
+ }
- return ret;
-}
+ ret.insert(defaultLocaleFamilyName);
+ QPlatformFontDatabase::registerFont(defaultLocaleFamilyName,
+ defaultLocaleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, defaultLocaleFamilyName));
+ }
-void QWindowsDirectWriteFontDatabase::releaseHandle(void *handle)
-{
- IDWriteFontFace *face = reinterpret_cast<IDWriteFontFace *>(handle);
- face->Release();
-}
+ if (!englishLocaleGdiCompatibleFamilyName.isEmpty() &&
+ !ret.contains(englishLocaleGdiCompatibleFamilyName)) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = englishLocaleGdiCompatibleFamilyName;
+ applicationFont->properties.append(properties);
+ }
-bool QWindowsDirectWriteFontDatabase::fontsAlwaysScalable() const
-{
- return true;
+ ret.insert(englishLocaleGdiCompatibleFamilyName);
+ QPlatformFontDatabase::registerFont(englishLocaleGdiCompatibleFamilyName,
+ englishLocaleGdiCompatibleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, englishLocaleGdiCompatibleFamilyName));
+ }
+
+ if (!defaultLocaleGdiCompatibleFamilyName.isEmpty()
+ && !ret.contains(defaultLocaleGdiCompatibleFamilyName)) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = defaultLocaleGdiCompatibleFamilyName;
+ applicationFont->properties.append(properties);
+ }
+
+ ret.insert(defaultLocaleGdiCompatibleFamilyName);
+ QPlatformFontDatabase::registerFont(defaultLocaleGdiCompatibleFamilyName,
+ defaultLocaleGdiCompatibleStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, defaultLocaleGdiCompatibleFamilyName));
+ }
+
+ if (!englishLocaleTypographicFamilyName.isEmpty()
+ && !ret.contains(englishLocaleTypographicFamilyName)) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = englishLocaleTypographicFamilyName;
+ applicationFont->properties.append(properties);
+ }
+
+ ret.insert(englishLocaleTypographicFamilyName);
+ QPlatformFontDatabase::registerFont(englishLocaleTypographicFamilyName,
+ englishLocaleTypographicStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, englishLocaleTypographicFamilyName));
+ }
+
+ if (!defaultLocaleTypographicFamilyName.isEmpty()
+ && !ret.contains(defaultLocaleTypographicFamilyName)) {
+ if (applicationFont != nullptr) {
+ QFontDatabasePrivate::ApplicationFont::Properties properties;
+ properties.style = style;
+ properties.weight = weight;
+ properties.familyName = defaultLocaleTypographicFamilyName;
+ applicationFont->properties.append(properties);
+ }
+
+ ret.insert(defaultLocaleTypographicFamilyName);
+ QPlatformFontDatabase::registerFont(defaultLocaleTypographicFamilyName,
+ defaultLocaleTypographicStyleName,
+ QString(),
+ weight,
+ style,
+ stretch,
+ antialias,
+ scalable,
+ size,
+ fixed,
+ writingSystems,
+ new FontHandle(face, defaultLocaleTypographicFamilyName));
+ }
+
+ } else {
+ qCWarning(lcQpaFonts) << "Unable to query IDWriteFontFace3 interface from font face.";
+ }
+
+ face->Release();
+ }
+
+ return ret.values();
}
bool QWindowsDirectWriteFontDatabase::isPrivateFontFamily(const QString &family) const
@@ -362,62 +690,103 @@ bool QWindowsDirectWriteFontDatabase::isPrivateFontFamily(const QString &family)
return false;
}
+static int QT_WIN_CALLBACK populateBitmapFonts(const LOGFONT *logFont,
+ const TEXTMETRIC *textmetric,
+ DWORD type,
+ LPARAM lparam)
+{
+ Q_UNUSED(textmetric);
+
+ // the "@family" fonts are just the same as "family". Ignore them.
+ const ENUMLOGFONTEX *f = reinterpret_cast<const ENUMLOGFONTEX *>(logFont);
+ const wchar_t *faceNameW = f->elfLogFont.lfFaceName;
+ if (faceNameW[0] && faceNameW[0] != L'@' && wcsncmp(faceNameW, L"WST_", 4)) {
+ const QString faceName = QString::fromWCharArray(faceNameW);
+ if (type & RASTER_FONTTYPE || type == 0) {
+ QWindowsDirectWriteFontDatabase *db = reinterpret_cast<QWindowsDirectWriteFontDatabase *>(lparam);
+ if (!db->hasPopulatedFont(faceName)) {
+ db->registerFontFamily(faceName);
+ db->registerBitmapFont(faceName);
+ }
+ }
+ }
+ return 1; // continue
+}
+
void QWindowsDirectWriteFontDatabase::populateFontDatabase()
{
wchar_t defaultLocale[LOCALE_NAME_MAX_LENGTH];
bool hasDefaultLocale = GetUserDefaultLocaleName(defaultLocale, LOCALE_NAME_MAX_LENGTH) != 0;
wchar_t englishLocale[] = L"en-us";
- const QString defaultFontName = defaultFont().families().first();
- const QString systemDefaultFontName = systemDefaultFont().families().first();
+ const QString defaultFontName = defaultFont().families().constFirst();
+ const QString systemDefaultFontName = systemDefaultFont().families().constFirst();
+
+ DirectWriteScope<IDWriteFontCollection2> fontCollection;
+ DirectWriteScope<IDWriteFactory6> factory6;
+ if (FAILED(data()->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory6),
+ reinterpret_cast<void **>(&factory6)))) {
+ qCWarning(lcQpaFonts) << "Can't initialize IDWriteFactory6. Use GDI font engine instead.";
+ return;
+ }
- IDWriteFontCollection *fontCollection;
- if (SUCCEEDED(data()->directWriteFactory->GetSystemFontCollection(&fontCollection))) {
+ if (SUCCEEDED(factory6->GetSystemFontCollection(false,
+ DWRITE_FONT_FAMILY_MODEL_TYPOGRAPHIC,
+ &fontCollection))) {
for (uint i = 0; i < fontCollection->GetFontFamilyCount(); ++i) {
- IDWriteFontFamily *fontFamily;
+ DirectWriteScope<IDWriteFontFamily2> fontFamily;
if (SUCCEEDED(fontCollection->GetFontFamily(i, &fontFamily))) {
QString defaultLocaleName;
QString englishLocaleName;
- IDWriteLocalizedStrings *names;
+ DirectWriteScope<IDWriteLocalizedStrings> names;
if (SUCCEEDED(fontFamily->GetFamilyNames(&names))) {
if (hasDefaultLocale)
- defaultLocaleName = localeString(names, defaultLocale);
+ defaultLocaleName = localeString(*names, defaultLocale);
- englishLocaleName = localeString(names, englishLocale);
+ englishLocaleName = localeString(*names, englishLocale);
}
qCDebug(lcQpaFonts) << "Registering font, english name = " << englishLocaleName << ", name in current locale = " << defaultLocaleName;
if (!defaultLocaleName.isEmpty()) {
registerFontFamily(defaultLocaleName);
- m_populatedFonts.insert(defaultLocaleName, fontFamily);
+ m_populatedFonts.insert(defaultLocaleName, *fontFamily);
fontFamily->AddRef();
if (defaultLocaleName == defaultFontName && defaultFontName != systemDefaultFontName) {
qDebug(lcQpaFonts) << "Adding default font" << systemDefaultFontName << "as alternative to" << defaultLocaleName;
- m_populatedFonts.insert(systemDefaultFontName, fontFamily);
+ m_populatedFonts.insert(systemDefaultFontName, *fontFamily);
fontFamily->AddRef();
}
}
if (!englishLocaleName.isEmpty() && englishLocaleName != defaultLocaleName) {
registerFontFamily(englishLocaleName);
- m_populatedFonts.insert(englishLocaleName, fontFamily);
+ m_populatedFonts.insert(englishLocaleName, *fontFamily);
fontFamily->AddRef();
if (englishLocaleName == defaultFontName && defaultFontName != systemDefaultFontName) {
qDebug(lcQpaFonts) << "Adding default font" << systemDefaultFontName << "as alternative to" << englishLocaleName;
- m_populatedFonts.insert(systemDefaultFontName, fontFamily);
+ m_populatedFonts.insert(systemDefaultFontName, *fontFamily);
fontFamily->AddRef();
}
}
-
- fontFamily->Release();
}
}
}
+
+ // Since bitmap fonts are not supported by DirectWrite, we need to populate these as well
+ {
+ HDC dummy = GetDC(0);
+ LOGFONT lf;
+ lf.lfCharSet = DEFAULT_CHARSET;
+ lf.lfFaceName[0] = 0;
+ lf.lfPitchAndFamily = 0;
+ EnumFontFamiliesEx(dummy, &lf, populateBitmapFonts, reinterpret_cast<intptr_t>(this), 0);
+ ReleaseDC(0, dummy);
+ }
}
QFont QWindowsDirectWriteFontDatabase::defaultFont() const
@@ -425,4 +794,16 @@ QFont QWindowsDirectWriteFontDatabase::defaultFont() const
return QFont(QStringLiteral("Segoe UI"));
}
+bool QWindowsDirectWriteFontDatabase::supportsVariableApplicationFonts() const
+{
+ QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
+ DirectWriteScope<IDWriteFactory5> factory5;
+ if (SUCCEEDED(fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory5),
+ reinterpret_cast<void **>(&factory5)))) {
+ return true;
+ }
+
+ return false;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h b/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
index 89b216a239..093c629a16 100644
--- a/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
+++ b/src/gui/text/windows/qwindowsdirectwritefontdatabase_p.h
@@ -20,17 +20,18 @@
QT_REQUIRE_CONFIG(directwrite3);
-#include "qwindowsfontdatabasebase_p.h"
+#include "qwindowsfontdatabase_p.h"
#include <QtCore/qloggingcategory.h>
struct IDWriteFactory;
struct IDWriteFont;
+struct IDWriteFont1;
struct IDWriteFontFamily;
struct IDWriteLocalizedStrings;
QT_BEGIN_NAMESPACE
-class Q_GUI_EXPORT QWindowsDirectWriteFontDatabase : public QWindowsFontDatabaseBase
+class Q_GUI_EXPORT QWindowsDirectWriteFontDatabase : public QWindowsFontDatabase
{
Q_DISABLE_COPY_MOVE(QWindowsDirectWriteFontDatabase)
public:
@@ -39,19 +40,34 @@ public:
void populateFontDatabase() override;
void populateFamily(const QString &familyName) override;
+ bool populateFamilyAliases(const QString &missingFamily) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
+ QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const override;
QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *font = nullptr) override;
- void releaseHandle(void *handle) override;
QFont defaultFont() const override;
- bool fontsAlwaysScalable() const override;
bool isPrivateFontFamily(const QString &family) const override;
+ bool supportsVariableApplicationFonts() const override;
+
+ void registerBitmapFont(const QString &bitmapFont)
+ {
+ m_populatedBitmapFonts.insert(bitmapFont);
+ }
+
+ bool hasPopulatedFont(const QString &fontFamily) const
+ {
+ return m_populatedFonts.contains(fontFamily);
+ }
private:
+ friend class QWindowsFontEngineDirectWrite;
static QString localeString(IDWriteLocalizedStrings *names, wchar_t localeName[]);
+ QSupportedWritingSystems supportedWritingSystems(IDWriteFontFace *face) const;
+
QHash<QString, IDWriteFontFamily *> m_populatedFonts;
+ QSet<QString> m_populatedBitmapFonts;
};
QT_END_NAMESPACE
diff --git a/src/gui/text/windows/qwindowsfontdatabase.cpp b/src/gui/text/windows/qwindowsfontdatabase.cpp
index 2de53be6a8..adc06a6c2a 100644
--- a/src/gui/text/windows/qwindowsfontdatabase.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabase.cpp
@@ -10,7 +10,6 @@
#include <QtGui/QFont>
#include <QtGui/QGuiApplication>
-#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtGui/private/qtgui-config_p.h>
#include <QtCore/qmath.h>
@@ -32,6 +31,8 @@
# include "qwindowsfontenginedirectwrite_p.h"
#endif
+#include <mutex>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -55,7 +56,7 @@ static inline bool useDirectWrite(QFont::HintingPreference hintingPreference,
return hintingPreference == QFont::PreferNoHinting
|| hintingPreference == QFont::PreferVerticalHinting
- || (QHighDpiScaling::isActive() && hintingPreference == QFont::PreferDefaultHinting);
+ || (!qFuzzyCompare(qApp->devicePixelRatio(), 1.0) && hintingPreference == QFont::PreferDefaultHinting);
}
#endif // !QT_NO_DIRECTWRITE
@@ -190,17 +191,6 @@ static inline QFontDatabase::WritingSystem writingSystemFromCharSet(uchar charSe
return QFontDatabase::Any;
}
-#ifdef MAKE_TAG
-#undef MAKE_TAG
-#endif
-// GetFontData expects the tags in little endian ;(
-#define MAKE_TAG(ch1, ch2, ch3, ch4) (\
- (((quint32)(ch4)) << 24) | \
- (((quint32)(ch3)) << 16) | \
- (((quint32)(ch2)) << 8) | \
- ((quint32)(ch1)) \
- )
-
bool qt_localizedName(const QString &name)
{
const QChar *c = name.unicode();
@@ -378,7 +368,7 @@ QString qt_getEnglishName(const QString &familyName, bool includeStyle)
HGDIOBJ oldobj = SelectObject( hdc, hfont );
- const DWORD name_tag = MAKE_TAG( 'n', 'a', 'm', 'e' );
+ const DWORD name_tag = qFromBigEndian(QFont::Tag("name").value());
// get the name table
unsigned char *table = 0;
@@ -427,7 +417,7 @@ QFontNames qt_getCanonicalFontNames(const LOGFONT &lf)
// get the name table
QByteArray table;
- const DWORD name_tag = MAKE_TAG('n', 'a', 'm', 'e');
+ const DWORD name_tag = qFromBigEndian(QFont::Tag("name").value());
DWORD bytes = GetFontData(hdc, name_tag, 0, 0, 0);
if (bytes != GDI_ERROR) {
table.resize(bytes);
@@ -443,18 +433,6 @@ QFontNames qt_getCanonicalFontNames(const LOGFONT &lf)
return fontNames;
}
-static QChar *createFontFile(const QString &faceName)
-{
- QChar *faceNamePtr = nullptr;
- if (!faceName.isEmpty()) {
- const int nameLength = qMin(faceName.length(), LF_FACESIZE - 1);
- faceNamePtr = new QChar[nameLength + 1];
- memcpy(static_cast<void *>(faceNamePtr), faceName.data(), sizeof(wchar_t) * nameLength);
- faceNamePtr[nameLength] = u'\0';
- }
- return faceNamePtr;
-}
-
namespace {
struct StoreFontPayload {
StoreFontPayload(const QString &family,
@@ -561,33 +539,35 @@ static bool addFontToDatabase(QString familyName,
writingSystems.setSupported(ws);
}
- // We came here from populating a different font family, so we have
- // to ensure the entire typographic family is populated before we
- // mark it as such inside registerFont()
- if (!subFamilyName.isEmpty()
- && familyName != subFamilyName
- && sfp->populatedFontFamily != familyName
- && !QPlatformFontDatabase::isFamilyPopulated(familyName)) {
- sfp->windowsFontDatabase->populateFamily(familyName);
- }
-
+ const bool wasPopulated = QPlatformFontDatabase::isFamilyPopulated(familyName);
QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight,
- style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName));
+ style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
+
// add fonts windows can generate for us:
if (weight <= QFont::DemiBold && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold,
- style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName));
+ style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
if (style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, weight,
- QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName));
+ QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
if (weight <= QFont::DemiBold && style != QFont::StyleItalic && styleName.isEmpty())
QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold,
- QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName));
+ QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
+
+ // We came here from populating a different font family, so we have
+ // to ensure the entire typographic family is populated before we
+ // mark it as such inside registerFont()
+ if (!subFamilyName.isEmpty()
+ && familyName != subFamilyName
+ && sfp->populatedFontFamily != familyName
+ && !wasPopulated) {
+ sfp->windowsFontDatabase->populateFamily(familyName);
+ }
if (!subFamilyName.isEmpty() && familyName != subFamilyName) {
QPlatformFontDatabase::registerFont(subFamilyName, subFamilyStyle, foundryName, weight,
- style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName));
+ style, stretch, antialias, scalable, size, fixed, writingSystems, new QWindowsFontDatabase::FontHandle(faceName));
}
if (!englishName.isEmpty() && englishName != familyName)
@@ -725,7 +705,7 @@ void QWindowsFontDatabase::populateFontDatabase()
EnumFontFamiliesEx(dummy, &lf, populateFontFamilies, 0, 0);
ReleaseDC(0, dummy);
// Work around EnumFontFamiliesEx() not listing the system font.
- const QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().families().first();
+ const QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().families().constFirst();
if (QPlatformFontDatabase::resolveFontFamilyAlias(systemDefaultFamily) == systemDefaultFamily)
QPlatformFontDatabase::registerFontFamily(systemDefaultFamily);
addDefaultEUDCFont();
@@ -733,6 +713,7 @@ void QWindowsFontDatabase::populateFontDatabase()
void QWindowsFontDatabase::invalidate()
{
+ QWindowsFontDatabaseBase::invalidate();
removeApplicationFonts();
}
@@ -758,7 +739,8 @@ QWindowsFontDatabase::~QWindowsFontDatabase()
QFontEngine * QWindowsFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
{
- const QString faceName(static_cast<const QChar*>(handle));
+ FontHandle *fontHandle = static_cast<FontHandle *>(handle);
+ const QString faceName = fontHandle->faceName.left(LF_FACESIZE - 1);
QFontEngine *fe = QWindowsFontDatabase::createEngine(fontDef, faceName,
defaultVerticalDPI(),
data());
@@ -820,7 +802,7 @@ QT_WARNING_POP
if (fontEngine) {
if (request.families != fontEngine->fontDef.families) {
qWarning("%s: Failed to load font. Got fallback instead: %s", __FUNCTION__,
- qPrintable(fontEngine->fontDef.families.first()));
+ qPrintable(fontEngine->fontDef.families.constFirst()));
if (fontEngine->ref.loadRelaxed() == 0)
delete fontEngine;
fontEngine = 0;
@@ -844,10 +826,13 @@ QT_WARNING_POP
Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled font engine.");
}
- UniqueFontData uniqueData;
+ UniqueFontData uniqueData{};
uniqueData.handle = fontHandle;
- uniqueData.refCount.ref();
- m_uniqueFontData[uniqueFamilyName] = uniqueData;
+ ++uniqueData.refCount;
+ {
+ const std::scoped_lock lock(m_uniqueFontDataMutex);
+ m_uniqueFontData[uniqueFamilyName] = uniqueData;
+ }
}
} else {
RemoveFontMemResourceEx(fontHandle);
@@ -868,36 +853,70 @@ QT_WARNING_POP
return fontEngine;
}
-static QList<quint32> getTrueTypeFontOffsets(const uchar *fontData)
+static QList<quint32> getTrueTypeFontOffsets(const uchar *fontData, const uchar *fileEndSentinel)
{
QList<quint32> offsets;
- const quint32 headerTag = *reinterpret_cast<const quint32 *>(fontData);
- if (headerTag != MAKE_TAG('t', 't', 'c', 'f')) {
- if (headerTag != MAKE_TAG(0, 1, 0, 0)
- && headerTag != MAKE_TAG('O', 'T', 'T', 'O')
- && headerTag != MAKE_TAG('t', 'r', 'u', 'e')
- && headerTag != MAKE_TAG('t', 'y', 'p', '1'))
+ if (fileEndSentinel - fontData < 12) {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
+ return offsets;
+ }
+
+ const quint32 headerTag = qFromUnaligned<quint32>(fontData);
+ if (headerTag != qFromBigEndian(QFont::Tag("ttcf").value())) {
+ if (headerTag != qFromBigEndian(QFont::Tag("\0\1\0\0").value())
+ && headerTag != qFromBigEndian(QFont::Tag("OTTO").value())
+ && headerTag != qFromBigEndian(QFont::Tag("true").value())
+ && headerTag != qFromBigEndian(QFont::Tag("typ1").value())) {
return offsets;
+ }
offsets << 0;
return offsets;
}
+
+ const quint32 maximumNumFonts = 0xffff;
const quint32 numFonts = qFromBigEndian<quint32>(fontData + 8);
- for (uint i = 0; i < numFonts; ++i) {
- offsets << qFromBigEndian<quint32>(fontData + 12 + i * 4);
+ if (numFonts > maximumNumFonts) {
+ qCWarning(lcQpaFonts) << "Font collection of" << numFonts << "fonts is too large. Aborting.";
+ return offsets;
+ }
+
+ if (quintptr(fileEndSentinel - fontData) > 12 + (numFonts - 1) * 4) {
+ for (quint32 i = 0; i < numFonts; ++i)
+ offsets << qFromBigEndian<quint32>(fontData + 12 + i * 4);
+ } else {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
}
+
return offsets;
}
-static void getFontTable(const uchar *fileBegin, const uchar *data, quint32 tag, const uchar **table, quint32 *length)
+static void getFontTable(const uchar *fileBegin, const uchar *fileEndSentinel, const uchar *data, quint32 tag, const uchar **table, quint32 *length)
{
- const quint16 numTables = qFromBigEndian<quint16>(data + 4);
- for (uint i = 0; i < numTables; ++i) {
- const quint32 offset = 12 + 16 * i;
- if (*reinterpret_cast<const quint32 *>(data + offset) == tag) {
- *table = fileBegin + qFromBigEndian<quint32>(data + offset + 8);
- *length = qFromBigEndian<quint32>(data + offset + 12);
- return;
+ if (fileEndSentinel - data >= 6) {
+ const quint16 numTables = qFromBigEndian<quint16>(data + 4);
+ if (fileEndSentinel - data >= 28 + 16 * (numTables - 1)) {
+ for (quint32 i = 0; i < numTables; ++i) {
+ const quint32 offset = 12 + 16 * i;
+ if (qFromUnaligned<quint32>(data + offset) == tag) {
+ const quint32 tableOffset = qFromBigEndian<quint32>(data + offset + 8);
+ if (quintptr(fileEndSentinel - fileBegin) <= tableOffset) {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
+ break;
+ }
+ *table = fileBegin + tableOffset;
+ *length = qFromBigEndian<quint32>(data + offset + 12);
+ if (quintptr(fileEndSentinel - *table) < *length) {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
+ break;
+ }
+ return;
+ }
+ }
+ } else {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
}
+ } else {
+ qCWarning(lcQpaFonts) << "Corrupted font data detected";
}
*table = 0;
*length = 0;
@@ -910,8 +929,9 @@ static void getFamiliesAndSignatures(const QByteArray &fontData,
QList<QFontValues> *values)
{
const uchar *data = reinterpret_cast<const uchar *>(fontData.constData());
+ const uchar *dataEndSentinel = data + fontData.size();
- QList<quint32> offsets = getTrueTypeFontOffsets(data);
+ QList<quint32> offsets = getTrueTypeFontOffsets(data, dataEndSentinel);
if (offsets.isEmpty())
return;
@@ -919,7 +939,9 @@ static void getFamiliesAndSignatures(const QByteArray &fontData,
const uchar *font = data + offsets.at(i);
const uchar *table;
quint32 length;
- getFontTable(data, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length);
+ getFontTable(data, dataEndSentinel, font,
+ qFromBigEndian(QFont::Tag("name").value()),
+ &table, &length);
if (!table)
continue;
QFontNames names = qt_getCanonicalFontNames(table, length);
@@ -928,8 +950,11 @@ static void getFamiliesAndSignatures(const QByteArray &fontData,
families->append(std::move(names));
- if (values || signatures)
- getFontTable(data, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length);
+ if (values || signatures) {
+ getFontTable(data, dataEndSentinel, font,
+ qFromBigEndian(QFont::Tag("OS/2").value()),
+ &table, &length);
+ }
if (values) {
QFontValues fontValues;
@@ -1087,10 +1112,22 @@ void QWindowsFontDatabase::removeApplicationFonts()
m_eudcFonts.clear();
}
+QWindowsFontDatabase::FontHandle::FontHandle(IDWriteFontFace *face, const QString &name)
+ : fontFace(face), faceName(name)
+{
+ fontFace->AddRef();
+}
+
+
+QWindowsFontDatabase::FontHandle::~FontHandle()
+{
+ if (fontFace != nullptr)
+ fontFace->Release();
+}
+
void QWindowsFontDatabase::releaseHandle(void *handle)
{
- const QChar *faceName = reinterpret_cast<const QChar *>(handle);
- delete[] faceName;
+ delete static_cast<FontHandle *>(handle);
}
QString QWindowsFontDatabase::fontDir() const
@@ -1107,18 +1144,22 @@ bool QWindowsFontDatabase::fontsAlwaysScalable() const
void QWindowsFontDatabase::derefUniqueFont(const QString &uniqueFont)
{
- if (m_uniqueFontData.contains(uniqueFont)) {
- if (!m_uniqueFontData[uniqueFont].refCount.deref()) {
- RemoveFontMemResourceEx(m_uniqueFontData[uniqueFont].handle);
- m_uniqueFontData.remove(uniqueFont);
+ const std::scoped_lock lock(m_uniqueFontDataMutex);
+ const auto it = m_uniqueFontData.find(uniqueFont);
+ if (it != m_uniqueFontData.end()) {
+ if (--it->refCount == 0) {
+ RemoveFontMemResourceEx(it->handle);
+ m_uniqueFontData.erase(it);
}
}
}
void QWindowsFontDatabase::refUniqueFont(const QString &uniqueFont)
{
- if (m_uniqueFontData.contains(uniqueFont))
- m_uniqueFontData[uniqueFont].refCount.ref();
+ const std::scoped_lock lock(m_uniqueFontDataMutex);
+ const auto it = m_uniqueFontData.find(uniqueFont);
+ if (it != m_uniqueFontData.end())
+ ++it->refCount;
}
QStringList QWindowsFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
@@ -1186,6 +1227,7 @@ QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request, const Q
HRESULT hr = data->directWriteGdiInterop->CreateFontFaceFromHdc(data->hdc, &directWriteFontFace);
if (SUCCEEDED(hr)) {
bool isColorFont = false;
+ bool needsSimulation = false;
#if QT_CONFIG(direct2d)
IDWriteFontFace2 *directWriteFontFace2 = nullptr;
if (SUCCEEDED(directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace2),
@@ -1193,10 +1235,12 @@ QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request, const Q
if (directWriteFontFace2->IsColorFont())
isColorFont = directWriteFontFace2->GetPaletteEntryCount() > 0;
+ needsSimulation = directWriteFontFace2->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE;
+
directWriteFontFace2->Release();
}
#endif // direct2d
- useDw = useDw || useDirectWrite(hintingPreference, fam, isColorFont);
+ useDw = useDw || useDirectWrite(hintingPreference, fam, isColorFont) || needsSimulation;
qCDebug(lcQpaFonts)
<< __FUNCTION__ << request.families.first() << request.pointSize << "pt"
<< "hintingPreference=" << hintingPreference << "color=" << isColorFont
@@ -1212,9 +1256,6 @@ QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request, const Q
QFontDef fontDef = request;
fontDef.families = QStringList(QString::fromWCharArray(n));
-
- if (isColorFont)
- fedw->glyphFormat = QFontEngine::Format_ARGB;
fedw->initFontInfo(fontDef, dpi);
fe = fedw;
}
diff --git a/src/gui/text/windows/qwindowsfontdatabase_ft.cpp b/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
index e62f5e54b1..0604a85e35 100644
--- a/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabase_ft.cpp
@@ -378,7 +378,7 @@ void QWindowsFontDatabaseFT::populateFontDatabase()
EnumFontFamiliesEx(dummy, &lf, populateFontFamilies, 0, 0);
ReleaseDC(0, dummy);
// Work around EnumFontFamiliesEx() not listing the system font
- const QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().families().first();
+ const QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().families().constFirst();
if (QPlatformFontDatabase::resolveFontFamilyAlias(systemDefaultFamily) == systemDefaultFamily)
QPlatformFontDatabase::registerFontFamily(systemDefaultFamily);
}
@@ -386,7 +386,7 @@ void QWindowsFontDatabaseFT::populateFontDatabase()
QFontEngine * QWindowsFontDatabaseFT::fontEngine(const QFontDef &fontDef, void *handle)
{
QFontEngine *fe = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
- qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDEF" << fontDef.families.first() << fe << handle;
+ qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDEF" << fontDef.families.constFirst() << fe << handle;
return fe;
}
diff --git a/src/gui/text/windows/qwindowsfontdatabase_p.h b/src/gui/text/windows/qwindowsfontdatabase_p.h
index 923f875336..0c99c91fde 100644
--- a/src/gui/text/windows/qwindowsfontdatabase_p.h
+++ b/src/gui/text/windows/qwindowsfontdatabase_p.h
@@ -21,6 +21,7 @@
#include <QtCore/QSharedPointer>
#include <QtCore/QLoggingCategory>
#include <QtCore/qhashfunctions.h>
+#include <QtCore/qmutex.h>
#include <QtCore/qt_windows.h>
QT_BEGIN_NAMESPACE
@@ -44,6 +45,7 @@ public:
void populateFontDatabase() override;
void invalidate() override;
+ void removeApplicationFonts();
void populateFamily(const QString &familyName) override;
bool populateFamilyAliases(const QString &missingFamily) override;
@@ -73,8 +75,16 @@ public:
static void debugFormat(QDebug &d, const LOGFONT &lf);
#endif // !QT_NO_DEBUG_STREAM
+ struct FontHandle {
+ FontHandle(const QString &name) : faceName(name) {}
+ FontHandle(IDWriteFontFace *face, const QString &name);
+ ~FontHandle();
+
+ IDWriteFontFace *fontFace = nullptr;
+ QString faceName;
+ };
+
private:
- void removeApplicationFonts();
void addDefaultEUDCFont();
struct WinApplicationFont {
@@ -86,9 +96,10 @@ private:
struct UniqueFontData {
HANDLE handle;
- QAtomicInt refCount;
+ int refCount;
};
+ QMutex m_uniqueFontDataMutex; // protects m_uniqueFontData
QMap<QString, UniqueFontData> m_uniqueFontData;
static unsigned m_fontOptions;
diff --git a/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/src/gui/text/windows/qwindowsfontdatabasebase.cpp
index f9b36b4852..84e619b0d9 100644
--- a/src/gui/text/windows/qwindowsfontdatabasebase.cpp
+++ b/src/gui/text/windows/qwindowsfontdatabasebase.cpp
@@ -359,10 +359,10 @@ namespace {
{
}
- inline void addKey(const void *key, const QByteArray &fontData)
+ inline void addKey(const QByteArray &fontData)
{
- Q_ASSERT(!m_fontDatas.contains(key));
- m_fontDatas.insert(key, fontData);
+ if (!m_fontDatas.contains(fontData.data()))
+ m_fontDatas.insert(fontData.data(), fontData);
}
inline void removeKey(const void *key)
@@ -378,6 +378,11 @@ namespace {
UINT32 fontFileReferenceKeySize,
OUT IDWriteFontFileStream **fontFileStream) override;
+ void clear()
+ {
+ m_fontDatas.clear();
+ }
+
private:
ULONG m_referenceCount;
QHash<const void *, QByteArray> m_fontDatas;
@@ -435,52 +440,62 @@ namespace {
return S_OK;
}
- class CustomFontFileLoader
+} // Anonymous namespace
+
+class QCustomFontFileLoader
+{
+public:
+ QCustomFontFileLoader(IDWriteFactory *factory)
{
- public:
- CustomFontFileLoader(IDWriteFactory *factory)
- {
- m_directWriteFactory = factory;
+ m_directWriteFactory = factory;
- if (m_directWriteFactory) {
- m_directWriteFactory->AddRef();
+ if (m_directWriteFactory) {
+ m_directWriteFactory->AddRef();
- m_directWriteFontFileLoader = new DirectWriteFontFileLoader();
- m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader);
- }
+ m_directWriteFontFileLoader = new DirectWriteFontFileLoader();
+ m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader);
}
+ }
- ~CustomFontFileLoader()
- {
- if (m_directWriteFactory != nullptr && m_directWriteFontFileLoader != nullptr)
- m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader);
+ ~QCustomFontFileLoader()
+ {
+ clear();
- if (m_directWriteFactory != nullptr)
- m_directWriteFactory->Release();
- }
+ if (m_directWriteFactory != nullptr && m_directWriteFontFileLoader != nullptr)
+ m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader);
- void addKey(const void *key, const QByteArray &fontData)
- {
- if (m_directWriteFontFileLoader != nullptr)
- m_directWriteFontFileLoader->addKey(key, fontData);
- }
+ if (m_directWriteFactory != nullptr)
+ m_directWriteFactory->Release();
+ }
- void removeKey(const void *key)
- {
- if (m_directWriteFontFileLoader != nullptr)
- m_directWriteFontFileLoader->removeKey(key);
- }
+ void addKey(const QByteArray &fontData)
+ {
+ if (m_directWriteFontFileLoader != nullptr)
+ m_directWriteFontFileLoader->addKey(fontData);
+ }
- IDWriteFontFileLoader *loader() const
- {
- return m_directWriteFontFileLoader;
- }
+ void removeKey(const void *key)
+ {
+ if (m_directWriteFontFileLoader != nullptr)
+ m_directWriteFontFileLoader->removeKey(key);
+ }
+
+ IDWriteFontFileLoader *loader() const
+ {
+ return m_directWriteFontFileLoader;
+ }
+
+ void clear()
+ {
+ if (m_directWriteFontFileLoader != nullptr)
+ m_directWriteFontFileLoader->clear();
+ }
+
+private:
+ IDWriteFactory *m_directWriteFactory = nullptr;
+ DirectWriteFontFileLoader *m_directWriteFontFileLoader = nullptr;
+};
- private:
- IDWriteFactory *m_directWriteFactory = nullptr;
- DirectWriteFontFileLoader *m_directWriteFontFileLoader = nullptr;
- };
-} // Anonymous namespace
#endif // directwrite && direct2d
@@ -550,12 +565,27 @@ void QWindowsFontDatabaseBase::createDirectWriteFactory(IDWriteFactory **factory
IUnknown *result = nullptr;
# if QT_CONFIG(directwrite3)
- DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
+ qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory6";
+ DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory6), &result);
+
+ if (result == nullptr) {
+ qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory5";
+ DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), &result);
+ }
+
+ if (result == nullptr) {
+ qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory3";
+ DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
+ }
# endif
- if (result == nullptr)
+
+ if (result == nullptr) {
+ qCDebug(lcQpaFonts) << "Trying to create IDWriteFactory2";
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
+ }
if (result == nullptr) {
+ qCDebug(lcQpaFonts) << "Trying to create plain IDWriteFactory";
if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
qErrnoWarning("DWriteCreateFactory failed");
return;
@@ -691,28 +721,47 @@ QFont QWindowsFontDatabaseBase::systemDefaultFont()
return systemFont;
}
+void QWindowsFontDatabaseBase::invalidate()
+{
+#if QT_CONFIG(directwrite)
+ m_fontFileLoader.reset(nullptr);
+#endif
+}
+
#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
-IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData) const
+IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData)
{
+ QList<IDWriteFontFace *> faces = createDirectWriteFaces(fontData, false);
+ Q_ASSERT(faces.size() <= 1);
+
+ return faces.isEmpty() ? nullptr : faces.first();
+}
+
+QList<IDWriteFontFace *> QWindowsFontDatabaseBase::createDirectWriteFaces(const QByteArray &fontData,
+ bool queryVariations) const
+{
+ QList<IDWriteFontFace *> ret;
QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
if (fontEngineData->directWriteFactory == nullptr) {
qCWarning(lcQpaFonts) << "DirectWrite factory not created in QWindowsFontDatabaseBase::createDirectWriteFace()";
- return nullptr;
+ return ret;
}
- CustomFontFileLoader fontFileLoader(fontEngineData->directWriteFactory);
- fontFileLoader.addKey(this, fontData);
+ if (m_fontFileLoader == nullptr)
+ m_fontFileLoader.reset(new QCustomFontFileLoader(fontEngineData->directWriteFactory));
+
+ m_fontFileLoader->addKey(fontData);
IDWriteFontFile *fontFile = nullptr;
- const void *key = this;
+ const void *key = fontData.data();
HRESULT hres = fontEngineData->directWriteFactory->CreateCustomFontFileReference(&key,
sizeof(void *),
- fontFileLoader.loader(),
+ m_fontFileLoader->loader(),
&fontFile);
if (FAILED(hres)) {
qErrnoWarning(hres, "%s: CreateCustomFontFileReference failed", __FUNCTION__);
- return nullptr;
+ return ret;
}
BOOL isSupportedFontType;
@@ -722,25 +771,65 @@ IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArra
fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces);
if (!isSupportedFontType) {
fontFile->Release();
- return nullptr;
+ return ret;
}
+#if QT_CONFIG(directwrite3)
+ IDWriteFactory5 *factory5 = nullptr;
+ if (queryVariations && SUCCEEDED(fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory5),
+ reinterpret_cast<void **>(&factory5)))) {
+
+ IDWriteFontSetBuilder1 *builder;
+ if (SUCCEEDED(factory5->CreateFontSetBuilder(&builder))) {
+ if (SUCCEEDED(builder->AddFontFile(fontFile))) {
+ IDWriteFontSet *fontSet;
+ if (SUCCEEDED(builder->CreateFontSet(&fontSet))) {
+ int count = fontSet->GetFontCount();
+ qCDebug(lcQpaFonts) << "Found" << count << "variations in font file";
+ for (int i = 0; i < count; ++i) {
+ IDWriteFontFaceReference *ref;
+ if (SUCCEEDED(fontSet->GetFontFaceReference(i, &ref))) {
+ IDWriteFontFace3 *face;
+ if (SUCCEEDED(ref->CreateFontFace(&face))) {
+ ret.append(face);
+ }
+ ref->Release();
+ }
+ }
+ fontSet->Release();
+ }
+ }
+
+ builder->Release();
+ }
+
+ factory5->Release();
+ }
+#else
+ Q_UNUSED(queryVariations);
+#endif
+
// ### Currently no support for .ttc, but we could easily return a list here.
- IDWriteFontFace *directWriteFontFace = nullptr;
- hres = fontEngineData->directWriteFactory->CreateFontFace(fontFaceType,
- 1,
- &fontFile,
- 0,
- DWRITE_FONT_SIMULATIONS_NONE,
- &directWriteFontFace);
- if (FAILED(hres)) {
- qErrnoWarning(hres, "%s: CreateFontFace failed", __FUNCTION__);
- fontFile->Release();
- return nullptr;
+ if (ret.isEmpty()) {
+ IDWriteFontFace *directWriteFontFace = nullptr;
+ hres = fontEngineData->directWriteFactory->CreateFontFace(fontFaceType,
+ 1,
+ &fontFile,
+ 0,
+ DWRITE_FONT_SIMULATIONS_NONE,
+ &directWriteFontFace);
+ if (FAILED(hres)) {
+ qErrnoWarning(hres, "%s: CreateFontFace failed", __FUNCTION__);
+ fontFile->Release();
+ return ret;
+ } else {
+ ret.append(directWriteFontFace);
+ }
}
fontFile->Release();
- return directWriteFontFace;
+
+ return ret;
}
#endif // directwrite && direct2d
@@ -760,7 +849,10 @@ QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QByteArray &fontData, qr
if (fontEngineData->directWriteFactory == nullptr)
return nullptr;
- IDWriteFontFace *directWriteFontFace = createDirectWriteFace(fontData);
+ IDWriteFontFace * directWriteFontFace = createDirectWriteFace(fontData);
+ if (directWriteFontFace == nullptr)
+ return nullptr;
+
fontEngine = new QWindowsFontEngineDirectWrite(directWriteFontFace,
pixelSize,
fontEngineData);
diff --git a/src/gui/text/windows/qwindowsfontdatabasebase_p.h b/src/gui/text/windows/qwindowsfontdatabasebase_p.h
index 60acc5cb06..55a3363551 100644
--- a/src/gui/text/windows/qwindowsfontdatabasebase_p.h
+++ b/src/gui/text/windows/qwindowsfontdatabasebase_p.h
@@ -29,6 +29,10 @@
QT_BEGIN_NAMESPACE
+#if QT_CONFIG(directwrite)
+ class QCustomFontFileLoader;
+#endif
+
class QWindowsFontEngineData
{
Q_DISABLE_COPY_MOVE(QWindowsFontEngineData)
@@ -56,6 +60,8 @@ public:
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override;
+ void invalidate() override;
+
static int defaultVerticalDPI();
static QSharedPointer<QWindowsFontEngineData> data();
@@ -91,11 +97,17 @@ public:
protected:
#if QT_CONFIG(directwrite)
- IDWriteFontFace *createDirectWriteFace(const QByteArray &fontData) const;
+ QList<IDWriteFontFace *> createDirectWriteFaces(const QByteArray &fontData,
+ bool queryVariations = true) const;
+ IDWriteFontFace *createDirectWriteFace(const QByteArray &fontData);
#endif
private:
static bool init(QSharedPointer<QWindowsFontEngineData> data);
+
+#if QT_CONFIG(directwrite)
+ mutable std::unique_ptr<QCustomFontFileLoader> m_fontFileLoader;
+#endif
};
QT_END_NAMESPACE
diff --git a/src/gui/text/windows/qwindowsfontengine.cpp b/src/gui/text/windows/qwindowsfontengine.cpp
index 5aef72eab3..5de80dc8a3 100644
--- a/src/gui/text/windows/qwindowsfontengine.cpp
+++ b/src/gui/text/windows/qwindowsfontengine.cpp
@@ -104,7 +104,7 @@ void QWindowsFontEngine::getCMap()
SelectObject(hdc, hfont);
bool symb = false;
if (ttf) {
- cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p'));
+ cmapTable = getSfntTable(QFont::Tag("cmap").value());
cmap = QFontEngine::getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()),
cmapTable.size(), &symb, &cmapSize);
}
@@ -132,8 +132,9 @@ void QWindowsFontEngine::getCMap()
}
}
-int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout *glyphs) const
+int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLayout *glyphs, int *mappedGlyphs) const
{
+ *mappedGlyphs = 0;
int glyph_pos = 0;
{
if (symbol) {
@@ -143,6 +144,8 @@ int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLa
glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, cmapSize, uc);
if (!glyphs->glyphs[glyph_pos] && uc < 0x100)
glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, cmapSize, uc + 0xf000);
+ if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
+ (*mappedGlyphs)++;
++glyph_pos;
}
} else if (ttf) {
@@ -150,6 +153,8 @@ int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLa
while (it.hasNext()) {
const uint uc = it.next();
glyphs->glyphs[glyph_pos] = getTrueTypeGlyphIndex(cmap, cmapSize, uc);
+ if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
+ (*mappedGlyphs)++;
++glyph_pos;
}
} else {
@@ -160,6 +165,8 @@ int QWindowsFontEngine::getGlyphIndexes(const QChar *str, int numChars, QGlyphLa
glyphs->glyphs[glyph_pos] = uc;
else
glyphs->glyphs[glyph_pos] = 0;
+ if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
+ (*mappedGlyphs)++;
++glyph_pos;
}
}
@@ -259,7 +266,7 @@ HGDIOBJ QWindowsFontEngine::selectDesignFont() const
return SelectObject(m_fontEngineData->hdc, designFont);
}
-bool QWindowsFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const
+int QWindowsFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const
{
Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
if (*nglyphs < len) {
@@ -268,12 +275,13 @@ bool QWindowsFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *g
}
glyphs->numGlyphs = *nglyphs;
- *nglyphs = getGlyphIndexes(str, len, glyphs);
+ int mappedGlyphs;
+ *nglyphs = getGlyphIndexes(str, len, glyphs, &mappedGlyphs);
if (!(flags & GlyphIndicesOnly))
recalcAdvances(glyphs, flags);
- return true;
+ return mappedGlyphs;
}
inline void calculateTTFGlyphWidth(HDC hdc, UINT glyph, int &width)
@@ -471,7 +479,7 @@ namespace {
QFixed QWindowsFontEngine::capHeight() const
{
- const QByteArray tableData = getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
+ const QByteArray tableData = getSfntTable(QFont::Tag("OS/2").value());
if (size_t(tableData.size()) >= sizeof(OS2Table)) {
const OS2Table *table = reinterpret_cast<const OS2Table *>(tableData.constData());
if (qFromBigEndian<quint16>(table->version) >= 2) {
@@ -1089,7 +1097,7 @@ QImage QWindowsFontEngine::alphaRGBMapForGlyph(glyph_t glyph,
QFontEngine *QWindowsFontEngine::cloneWithSize(qreal pixelSize) const
{
QFontDef request = fontDef;
- QString actualFontName = request.families.first();
+ QString actualFontName = request.families.constFirst();
if (!uniqueFamilyName.isEmpty())
request.families = QStringList(uniqueFamilyName);
request.pixelSize = pixelSize;
diff --git a/src/gui/text/windows/qwindowsfontengine_p.h b/src/gui/text/windows/qwindowsfontengine_p.h
index afe8ee4ca5..07f4db3c4a 100644
--- a/src/gui/text/windows/qwindowsfontengine_p.h
+++ b/src/gui/text/windows/qwindowsfontengine_p.h
@@ -48,7 +48,7 @@ public:
QFixed emSquareSize() const override;
glyph_t glyphIndex(uint ucs4) const override;
- bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
+ int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override;
void recalcAdvances(QGlyphLayout *glyphs, ShaperFlags) const override;
void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) override;
@@ -88,7 +88,7 @@ public:
bool hasUnreliableGlyphOutline() const override;
- int getGlyphIndexes(const QChar *ch, int numChars, QGlyphLayout *glyphs) const;
+ int getGlyphIndexes(const QChar *ch, int numChars, QGlyphLayout *glyphs, int *mappedGlyphs) const;
void getCMap();
bool getOutlineMetrics(glyph_t glyph, const QTransform &t, glyph_metrics_t *metrics) const;
diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
index 14dd064c33..47b8a7ee3c 100644
--- a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
+++ b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
@@ -12,10 +12,14 @@
#include <QtCore/private/qwinregistry_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
-#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtGui/qpainterpath.h>
-#include <dwrite_2.h>
+#if QT_CONFIG(directwrite3)
+# include "qwindowsdirectwritefontdatabase_p.h"
+# include <dwrite_3.h>
+#else
+# include <dwrite_2.h>
+#endif
#include <d2d1.h>
@@ -158,10 +162,13 @@ static DWRITE_MEASURING_MODE renderModeToMeasureMode(DWRITE_RENDERING_MODE rende
}
}
-static DWRITE_RENDERING_MODE hintingPreferenceToRenderingMode(const QFontDef &fontDef)
+DWRITE_RENDERING_MODE QWindowsFontEngineDirectWrite::hintingPreferenceToRenderingMode(const QFontDef &fontDef) const
{
+ if ((fontDef.styleStrategy & QFont::NoAntialias) && glyphFormat != QFontEngine::Format_ARGB)
+ return DWRITE_RENDERING_MODE_ALIASED;
+
QFont::HintingPreference hintingPreference = QFont::HintingPreference(fontDef.hintingPreference);
- if (QHighDpiScaling::isActive() && hintingPreference == QFont::PreferDefaultHinting) {
+ if (!qFuzzyCompare(qApp->devicePixelRatio(), 1.0) && hintingPreference == QFont::PreferDefaultHinting) {
// Microsoft documentation recommends using asymmetric rendering for small fonts
// at pixel size 16 and less, and symmetric for larger fonts.
hintingPreference = fontDef.pixelSize > 16.0
@@ -212,7 +219,7 @@ QWindowsFontEngineDirectWrite::QWindowsFontEngineDirectWrite(IDWriteFontFace *di
fontDef.pixelSize = pixelSize;
collectMetrics();
- cache_cost = (m_ascent.toInt() + m_descent.toInt()) * m_xHeight.toInt() * 2000;
+ cache_cost = m_xHeight.toInt() * m_xHeight.toInt() * 2000;
}
QWindowsFontEngineDirectWrite::~QWindowsFontEngineDirectWrite()
@@ -354,12 +361,14 @@ void QWindowsFontEngineDirectWrite::collectMetrics()
fontFile->Release();
}
- QByteArray table = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a'));
+ QByteArray table = getSfntTable(QFont::Tag("hhea").value());
const int advanceWidthMaxLocation = 10;
if (table.size() >= advanceWidthMaxLocation + int(sizeof(quint16))) {
quint16 advanceWidthMax = qFromBigEndian<quint16>(table.constData() + advanceWidthMaxLocation);
m_maxAdvanceWidth = DESIGN_TO_LOGICAL(advanceWidthMax);
}
+
+ loadKerningPairs(emSquareSize() / QFixed::fromReal(fontDef.pixelSize));
}
QFixed QWindowsFontEngineDirectWrite::underlinePosition() const
@@ -426,13 +435,13 @@ glyph_t QWindowsFontEngineDirectWrite::glyphIndex(uint ucs4) const
return glyphIndex;
}
-bool QWindowsFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
- int *nglyphs, QFontEngine::ShaperFlags flags) const
+int QWindowsFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs,
+ int *nglyphs, QFontEngine::ShaperFlags flags) const
{
Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
if (*nglyphs < len) {
*nglyphs = len;
- return false;
+ return -1;
}
QVarLengthArray<UINT32> codePoints(len);
@@ -446,11 +455,15 @@ bool QWindowsFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGly
glyphIndices.data());
if (FAILED(hr)) {
qErrnoWarning("%s: GetGlyphIndicesW failed", __FUNCTION__);
- return false;
+ return -1;
}
- for (int i = 0; i < actualLength; ++i)
+ int mappedGlyphs = 0;
+ for (int i = 0; i < actualLength; ++i) {
glyphs->glyphs[i] = glyphIndices.at(i);
+ if (glyphs->glyphs[i] != 0 || isIgnorableChar(codePoints.at(i)))
+ mappedGlyphs++;
+ }
*nglyphs = actualLength;
glyphs->numGlyphs = actualLength;
@@ -458,7 +471,7 @@ bool QWindowsFontEngineDirectWrite::stringToCMap(const QChar *str, int len, QGly
if (!(flags & GlyphIndicesOnly))
recalcAdvances(glyphs, {});
- return true;
+ return mappedGlyphs;
}
QFontEngine::FaceId QWindowsFontEngineDirectWrite::faceId() const
@@ -466,7 +479,7 @@ QFontEngine::FaceId QWindowsFontEngineDirectWrite::faceId() const
return m_faceId;
}
-void QWindowsFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags) const
+void QWindowsFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags shaperFlags) const
{
QVarLengthArray<UINT16> glyphIndices(glyphs->numGlyphs);
@@ -478,11 +491,14 @@ void QWindowsFontEngineDirectWrite::recalcAdvances(QGlyphLayout *glyphs, QFontEn
HRESULT hr;
DWRITE_RENDERING_MODE renderMode = hintingPreferenceToRenderingMode(fontDef);
- if (renderMode == DWRITE_RENDERING_MODE_GDI_CLASSIC || renderMode == DWRITE_RENDERING_MODE_GDI_NATURAL) {
+ bool needsDesignMetrics = shaperFlags & QFontEngine::DesignMetrics;
+ if (!needsDesignMetrics && (renderMode == DWRITE_RENDERING_MODE_GDI_CLASSIC
+ || renderMode == DWRITE_RENDERING_MODE_GDI_NATURAL
+ || renderMode == DWRITE_RENDERING_MODE_ALIASED)) {
hr = m_directWriteFontFace->GetGdiCompatibleGlyphMetrics(float(fontDef.pixelSize),
1.0f,
NULL,
- TRUE,
+ renderMode == DWRITE_RENDERING_MODE_GDI_NATURAL,
glyphIndices.data(),
glyphIndices.size(),
glyphMetrics.data());
@@ -586,7 +602,9 @@ glyph_metrics_t QWindowsFontEngineDirectWrite::boundingBox(const QGlyphLayout &g
for (int i = 0; i < glyphs.numGlyphs; ++i)
w += glyphs.effectiveAdvance(i);
- return glyph_metrics_t(0, -ascent(), w - lastRightBearing(glyphs), ascent() + descent(), w, 0);
+ const QFixed leftBearing = firstLeftBearing(glyphs);
+ return glyph_metrics_t(leftBearing, -ascent(), w - leftBearing - lastRightBearing(glyphs),
+ ascent() + descent(), w, 0);
}
glyph_metrics_t QWindowsFontEngineDirectWrite::boundingBox(glyph_t g)
@@ -665,7 +683,10 @@ QImage QWindowsFontEngineDirectWrite::alphaMapForGlyph(glyph_t glyph,
bool QWindowsFontEngineDirectWrite::supportsHorizontalSubPixelPositions() const
{
- return true;
+ DWRITE_RENDERING_MODE renderMode = hintingPreferenceToRenderingMode(fontDef);
+ return (renderMode != DWRITE_RENDERING_MODE_GDI_CLASSIC
+ && renderMode != DWRITE_RENDERING_MODE_GDI_NATURAL
+ && renderMode != DWRITE_RENDERING_MODE_ALIASED);
}
QFontEngine::Properties QWindowsFontEngineDirectWrite::properties() const
@@ -768,7 +789,10 @@ QImage QWindowsFontEngineDirectWrite::imageForGlyph(glyph_t t,
if (SUCCEEDED(hr)) {
RECT rect;
- glyphAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &rect);
+ glyphAnalysis->GetAlphaTextureBounds(renderMode == DWRITE_RENDERING_MODE_ALIASED
+ ? DWRITE_TEXTURE_ALIASED_1x1
+ : DWRITE_TEXTURE_CLEARTYPE_3x1,
+ &rect);
if (rect.top == rect.bottom || rect.left == rect.right)
return QImage();
@@ -849,7 +873,8 @@ QImage QWindowsFontEngineDirectWrite::imageForGlyph(glyph_t t,
b,
a,
colorGlyphsAnalysis,
- boundingRect);
+ boundingRect,
+ renderMode);
}
colorGlyphsAnalysis->Release();
@@ -876,7 +901,8 @@ QImage QWindowsFontEngineDirectWrite::imageForGlyph(glyph_t t,
b,
a,
glyphAnalysis,
- boundingRect);
+ boundingRect,
+ renderMode);
}
glyphAnalysis->Release();
@@ -894,7 +920,8 @@ void QWindowsFontEngineDirectWrite::renderGlyphRun(QImage *destination,
float b,
float a,
IDWriteGlyphRunAnalysis *glyphAnalysis,
- const QRect &boundingRect)
+ const QRect &boundingRect,
+ DWRITE_RENDERING_MODE renderMode)
{
const int width = destination->width();
const int height = destination->height();
@@ -915,12 +942,14 @@ void QWindowsFontEngineDirectWrite::renderGlyphRun(QImage *destination,
BYTE *alphaValues = alphaValueArray.data();
memset(alphaValues, 0, size);
- HRESULT hr = glyphAnalysis->CreateAlphaTexture(DWRITE_TEXTURE_CLEARTYPE_3x1,
+ HRESULT hr = glyphAnalysis->CreateAlphaTexture(renderMode == DWRITE_RENDERING_MODE_ALIASED
+ ? DWRITE_TEXTURE_ALIASED_1x1
+ : DWRITE_TEXTURE_CLEARTYPE_3x1,
&rect,
alphaValues,
size);
if (SUCCEEDED(hr)) {
- if (destination->hasAlphaChannel()) {
+ if (destination->hasAlphaChannel()) { // Color glyphs
for (int y = 0; y < height; ++y) {
uint *dest = reinterpret_cast<uint *>(destination->scanLine(y));
BYTE *src = alphaValues + width * 3 * y;
@@ -938,7 +967,16 @@ void QWindowsFontEngineDirectWrite::renderGlyphRun(QImage *destination,
qRound(qAlpha(currentRgb) * (1.0 - averageAlpha) + averageAlpha * 255));
}
}
+ } else if (renderMode == DWRITE_RENDERING_MODE_ALIASED) {
+ for (int y = 0; y < height; ++y) {
+ uint *dest = reinterpret_cast<uint *>(destination->scanLine(y));
+ BYTE *src = alphaValues + width * y;
+ for (int x = 0; x < width; ++x) {
+ int alpha = *(src++);
+ dest[x] = (alpha << 16) + (alpha << 8) + alpha;
+ }
+ }
} else {
for (int y = 0; y < height; ++y) {
uint *dest = reinterpret_cast<uint *>(destination->scanLine(y));
@@ -1007,6 +1045,27 @@ void QWindowsFontEngineDirectWrite::initFontInfo(const QFontDef &request,
fontDef.pointSize = fontDef.pixelSize * 72. / dpi;
else if (fontDef.pixelSize == -1)
fontDef.pixelSize = qRound(fontDef.pointSize * dpi / 72.);
+
+ m_faceId.variableAxes = request.variableAxisValues;
+
+#if QT_CONFIG(directwrite3)
+ IDWriteFontFace3 *face3 = nullptr;
+ if (SUCCEEDED(m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace3),
+ reinterpret_cast<void **>(&face3)))) {
+ IDWriteLocalizedStrings *names;
+ if (SUCCEEDED(face3->GetFaceNames(&names))) {
+ wchar_t englishLocale[] = L"en-us";
+ fontDef.styleName = QWindowsDirectWriteFontDatabase::localeString(names, englishLocale);
+ names->Release();
+ }
+
+ // Color font
+ if (face3->GetPaletteEntryCount() > 0)
+ glyphFormat = QFontEngine::Format_ARGB;
+
+ face3->Release();
+ }
+#endif
}
QString QWindowsFontEngineDirectWrite::fontNameSubstitute(const QString &familyName)
@@ -1092,7 +1151,7 @@ glyph_metrics_t QWindowsFontEngineDirectWrite::alphaMapBoundingBox(glyph_t glyph
if (SUCCEEDED(hr)) {
RECT rect;
- glyphAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &rect);
+ glyphAnalysis->GetAlphaTextureBounds(renderMode == DWRITE_RENDERING_MODE_ALIASED ? DWRITE_TEXTURE_ALIASED_1x1 : DWRITE_TEXTURE_CLEARTYPE_3x1, &rect);
glyphAnalysis->Release();
int margin = glyphMargin(format);
diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
index df6df1ad17..d7c9a79267 100644
--- a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
+++ b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
@@ -22,6 +22,7 @@ QT_REQUIRE_CONFIG(directwrite);
#include <QtGui/private/qfontengine_p.h>
#include <QtCore/QSharedPointer>
+#include <dwrite.h>
struct IDWriteFont;
struct IDWriteFontFace;
@@ -52,8 +53,8 @@ public:
QFixed emSquareSize() const override;
glyph_t glyphIndex(uint ucs4) const override;
- bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
- ShaperFlags flags) const override;
+ int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
+ ShaperFlags flags) const override;
void recalcAdvances(QGlyphLayout *glyphs, ShaperFlags) const override;
void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs,
@@ -108,8 +109,16 @@ private:
const QTransform &xform,
const QColor &color = QColor());
void collectMetrics();
- void renderGlyphRun(QImage *destination, float r, float g, float b, float a, IDWriteGlyphRunAnalysis *glyphAnalysis, const QRect &boundingRect);
+ void renderGlyphRun(QImage *destination,
+ float r,
+ float g,
+ float b,
+ float a,
+ IDWriteGlyphRunAnalysis *glyphAnalysis,
+ const QRect &boundingRect,
+ DWRITE_RENDERING_MODE renderMode);
static QString filenameFromFontFile(IDWriteFontFile *fontFile);
+ DWRITE_RENDERING_MODE hintingPreferenceToRenderingMode(const QFontDef &fontDef) const;
const QSharedPointer<QWindowsFontEngineData> m_fontEngineData;
diff --git a/src/gui/util/qastchandler.cpp b/src/gui/util/qastchandler.cpp
index f5c1d84f91..ec8b92f557 100644
--- a/src/gui/util/qastchandler.cpp
+++ b/src/gui/util/qastchandler.cpp
@@ -109,9 +109,9 @@ QTextureFileData QAstcHandler::read()
int zBlocks = (zSz + header->blockDimZ - 1) / header->blockDimZ;
int byteCount = 0;
- bool oob = mul_overflow(xBlocks, yBlocks, &byteCount)
- || mul_overflow(byteCount, zBlocks, &byteCount)
- || mul_overflow(byteCount, 16, &byteCount);
+ bool oob = qMulOverflow(xBlocks, yBlocks, &byteCount)
+ || qMulOverflow(byteCount, zBlocks, &byteCount)
+ || qMulOverflow(byteCount, 16, &byteCount);
res.setDataOffset(sizeof(AstcHeader));
diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp
index 4a12f6db6f..4d98faf398 100644
--- a/src/gui/util/qdesktopservices.cpp
+++ b/src/gui/util/qdesktopservices.cpp
@@ -18,8 +18,6 @@
#include <qpa/qplatformintegration.h>
#include <qdir.h>
-#include <QtCore/private/qlocking_p.h>
-
QT_BEGIN_NAMESPACE
class QOpenUrlHandlerRegistry
@@ -36,36 +34,10 @@ public:
};
typedef QHash<QString, Handler> HandlerHash;
HandlerHash handlers;
-
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
- QObject context;
-
- void handlerDestroyed(QObject *handler);
-#endif
-
};
Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry)
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
-void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler)
-{
- const auto lock = qt_scoped_lock(mutex);
- HandlerHash::Iterator it = handlers.begin();
- while (it != handlers.end()) {
- if (it->receiver == handler) {
- it = handlers.erase(it);
- qWarning("Please call QDesktopServices::unsetUrlHandler() before destroying a "
- "registered URL handler object.\n"
- "Support for destroying a registered URL handler object is deprecated, "
- "and will be removed in Qt 6.6.");
- } else {
- ++it;
- }
- }
-}
-#endif
-
/*!
\class QDesktopServices
\brief The QDesktopServices class provides methods for accessing common desktop services.
@@ -238,10 +210,11 @@ bool QDesktopServices::openUrl(const QUrl &url)
the destruction of the handler object does not overlap with concurrent
invocations of openUrl() using it.
- \section1 iOS
+ \section1 iOS and \macos
- To use this function for receiving data from other apps on iOS you also need to
- add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file:
+ To use this function for receiving data from other apps on iOS/\macos
+ you also need to add the custom scheme to the \c CFBundleURLSchemes
+ list in your Info.plist file:
\snippet code/src_gui_util_qdesktopservices.cpp 4
@@ -256,7 +229,7 @@ bool QDesktopServices::openUrl(const QUrl &url)
\snippet code/src_gui_util_qdesktopservices.cpp 7
- iOS will search for /.well-known/apple-app-site-association on your domain,
+ iOS/\macos will search for /.well-known/apple-app-site-association on your domain,
when the application is installed. If you want to listen to
\c{https://your.domain.com/help?topic=ABCDEF} you need to provide the following
content there:
@@ -306,11 +279,6 @@ void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, c
h.receiver = receiver;
h.name = method;
registry->handlers.insert(scheme.toLower(), h);
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
- QObject::connect(receiver, &QObject::destroyed, &registry->context,
- [registry](QObject *obj) { registry->handlerDestroyed(obj); },
- Qt::DirectConnection);
-#endif
}
/*!
diff --git a/src/gui/util/qgraphicsframecapture.cpp b/src/gui/util/qgraphicsframecapture.cpp
new file mode 100644
index 0000000000..e49fb83008
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapture.cpp
@@ -0,0 +1,123 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qgraphicsframecapture_p.h"
+#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library)
+#include "qgraphicsframecapturerenderdoc_p_p.h"
+#elif QT_CONFIG(metal)
+#include "qgraphicsframecapturemetal_p_p.h"
+#else
+#include "qgraphicsframecapture_p_p.h"
+#endif
+
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qcoreapplication.h>
+
+QT_BEGIN_NAMESPACE
+
+QGraphicsFrameCapturePrivate::QGraphicsFrameCapturePrivate()
+ : m_capturePath(QStandardPaths::writableLocation(QStandardPaths::TempLocation) +
+ QStringLiteral("/") + QCoreApplication::applicationName() +
+ QStringLiteral("/captures")),
+ m_capturePrefix(QCoreApplication::applicationName())
+{
+
+}
+
+QGraphicsFrameCapture::QGraphicsFrameCapture()
+{
+#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library)
+ d.reset(new QGraphicsFrameCaptureRenderDoc);
+#elif QT_CONFIG(metal)
+ d.reset(new QGraphicsFrameCaptureMetal);
+#endif
+}
+
+QGraphicsFrameCapture::~QGraphicsFrameCapture()
+{
+
+}
+
+void QGraphicsFrameCapture::setRhi(QRhi *rhi)
+{
+ if (!d.isNull())
+ d->setRhi(rhi);
+}
+
+void QGraphicsFrameCapture::startCaptureFrame()
+{
+ if (!d.isNull())
+ d->startCaptureFrame();
+}
+
+void QGraphicsFrameCapture::endCaptureFrame()
+{
+ if (!d.isNull())
+ d->endCaptureFrame();
+}
+
+QString QGraphicsFrameCapture::capturePath() const
+{
+ if (!d.isNull())
+ return d->capturePath();
+ return QString();
+}
+
+void QGraphicsFrameCapture::setCapturePath(const QString &path)
+{
+ if (!d.isNull())
+ d->setCapturePath(path);
+}
+
+QString QGraphicsFrameCapture::capturePrefix() const
+{
+ if (!d.isNull())
+ return d->capturePrefix();
+ return QString();
+}
+
+void QGraphicsFrameCapture::setCapturePrefix(const QString &prefix)
+{
+ if (!d.isNull())
+ d->setCapturePrefix(prefix);
+}
+
+QString QGraphicsFrameCapture::capturedFileName()
+{
+ if (!d.isNull())
+ return d->capturedFileName();
+
+ return QString();
+}
+
+QStringList QGraphicsFrameCapture::capturedFilesNames()
+{
+ if (!d.isNull())
+ return d->capturedFilesNames();
+
+ return QStringList();
+}
+
+bool QGraphicsFrameCapture::isLoaded() const
+{
+ if (!d.isNull())
+ return d->initialized();
+
+ return false;
+}
+
+bool QGraphicsFrameCapture::isCapturing() const
+{
+ if (!d.isNull())
+ return d->isCapturing();
+
+ return false;
+}
+
+void QGraphicsFrameCapture::openCapture() const
+{
+ if (!d.isNull())
+ d->openCapture();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qgraphicsframecapture_p.h b/src/gui/util/qgraphicsframecapture_p.h
new file mode 100644
index 0000000000..fef37d2869
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapture_p.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGRAPHICSFRAMECAPTURE_P_H
+#define QGRAPHICSFRAMECAPTURE_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 <QtCore/qscopedpointer.h>
+#include <QtGui/qtguiglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QGraphicsFrameCapturePrivate;
+class QRhi;
+
+class Q_GUI_EXPORT QGraphicsFrameCapture
+{
+public:
+ QGraphicsFrameCapture();
+ ~QGraphicsFrameCapture();
+
+ void setRhi(QRhi *rhi);
+ void startCaptureFrame();
+ void endCaptureFrame();
+
+ QString capturePath() const;
+ void setCapturePath(const QString &path);
+
+ QString capturePrefix() const;
+ void setCapturePrefix(const QString &prefix);
+
+ QString capturedFileName();
+ QStringList capturedFilesNames();
+
+ bool isLoaded() const;
+ bool isCapturing() const;
+ void openCapture() const;
+
+private:
+ QScopedPointer<QGraphicsFrameCapturePrivate> d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGRAPHICSFRAMECAPTURE_P_H
diff --git a/src/gui/util/qgraphicsframecapture_p_p.h b/src/gui/util/qgraphicsframecapture_p_p.h
new file mode 100644
index 0000000000..f8dbd2ca1d
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapture_p_p.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGRAPHICSFRAMECAPTURE_P_P_H
+#define QGRAPHICSFRAMECAPTURE_P_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 <QtCore/qnamespace.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcGraphicsFrameCapture)
+
+class QRhi;
+struct QRhiNativeHandles;
+
+class QGraphicsFrameCapturePrivate
+{
+public:
+ QGraphicsFrameCapturePrivate() ;
+ virtual ~QGraphicsFrameCapturePrivate() = default;
+
+ virtual void setRhi(QRhi *rhi) = 0;
+ virtual void startCaptureFrame() = 0;
+ virtual void endCaptureFrame() = 0;
+
+ QString capturePath() const { return m_capturePath; };
+ virtual void setCapturePath(const QString &path) { m_capturePath = path; }
+
+ QString capturePrefix() const { return m_capturePrefix; }
+ virtual void setCapturePrefix(const QString &prefix) { m_capturePrefix = prefix; }
+
+ virtual QString capturedFileName() const
+ {
+ return !m_capturedFilesNames.isEmpty() ? m_capturedFilesNames.last() : QString();
+ }
+ virtual QStringList capturedFilesNames() const { return m_capturedFilesNames; }
+
+ virtual bool initialized() const = 0;
+ virtual bool isCapturing() const = 0;
+ virtual void openCapture() = 0;
+
+protected:
+ QRhi *m_rhi = nullptr;
+ QRhiNativeHandles *m_rhiHandles = nullptr;
+ void *m_nativeHandle = nullptr;
+ QString m_capturePath;
+ QString m_capturePrefix;
+ QStringList m_capturedFilesNames;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGRAPHICSFRAMECAPTURE_P_P_H
diff --git a/src/gui/util/qgraphicsframecapturemetal.mm b/src/gui/util/qgraphicsframecapturemetal.mm
new file mode 100644
index 0000000000..b0ff0bab2b
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapturemetal.mm
@@ -0,0 +1,169 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qgraphicsframecapturemetal_p_p.h"
+#include <QtCore/qurl.h>
+#include "Metal/Metal.h"
+#include "qglobal.h"
+#include <QtGui/rhi/qrhi.h>
+#include <QtGui/rhi/qrhi_platform.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture")
+
+#if __has_feature(objc_arc)
+#error ARC not supported
+#endif
+
+uint QGraphicsFrameCaptureMetal::frameNumber = 0;
+
+QGraphicsFrameCaptureMetal::QGraphicsFrameCaptureMetal()
+{
+ qputenv("METAL_CAPTURE_ENABLED", QByteArrayLiteral("1"));
+
+ m_captureDescriptor = [MTLCaptureDescriptor new];
+}
+
+QGraphicsFrameCaptureMetal::~QGraphicsFrameCaptureMetal()
+{
+#if defined(Q_OS_MACOS) && QT_CONFIG(process)
+ if (m_process) {
+ m_process->terminate();
+ delete m_process;
+ }
+#endif
+ [m_captureDescriptor release];
+}
+
+void QGraphicsFrameCaptureMetal::setRhi(QRhi *rhi)
+{
+ if (!rhi)
+ return;
+
+ QRhi::Implementation backend = rhi->backend();
+ const QRhiNativeHandles *nh = rhi->nativeHandles();
+
+ switch (backend) {
+ case QRhi::Implementation::Metal: {
+ const QRhiMetalNativeHandles *mtlnh = static_cast<const QRhiMetalNativeHandles *>(nh);
+ if (mtlnh->cmdQueue) {
+ m_captureDescriptor.captureObject = mtlnh->cmdQueue;
+ } else if (mtlnh->dev) {
+ m_captureDescriptor.captureObject = mtlnh->dev;
+ } else {
+ qCWarning(lcGraphicsFrameCapture) << "No valid Metal Device or Metal Command Queue found";
+ m_initialized = false;
+ return;
+ }
+ break;
+ }
+ default: {
+ qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided. MTLCaptureManager works only with Metal API";
+ m_initialized = false;
+ return;
+ break;
+ }
+ }
+
+ if (!m_captureManager) {
+ m_captureManager = MTLCaptureManager.sharedCaptureManager;
+ bool supportDocs = [m_captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument];
+ if (supportDocs) {
+ m_captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument;
+ m_initialized = true;
+ }
+ }
+}
+
+void QGraphicsFrameCaptureMetal::startCaptureFrame()
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Starting capturing can not be done.";
+ return;
+ }
+
+ if (isCapturing()) {
+ qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress,"
+ "will not initiate another one until QGraphicsFrameCapture::endCaptureFrame is called.";
+ return;
+ }
+
+ updateCaptureFileName();
+ NSError *error;
+ if (![m_captureManager startCaptureWithDescriptor:m_captureDescriptor error:&error]) {
+ QString errorMsg = QString::fromNSString(error.localizedDescription);
+ qCWarning(lcGraphicsFrameCapture, "Failed to start capture : %s", qPrintable(errorMsg));
+ }
+}
+
+void QGraphicsFrameCaptureMetal::endCaptureFrame()
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. End capturing can not be done.";
+ return;
+ }
+
+ if (!isCapturing()) {
+ qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done"
+ " without a call to QGraphicsFrameCapture::startCaptureFrame";
+ return;
+ }
+
+ [m_captureManager stopCapture];
+ m_capturedFilesNames.append(QString::fromNSString(m_traceURL.path));
+ frameNumber++;
+}
+
+bool QGraphicsFrameCaptureMetal::initialized() const
+{
+ return m_initialized;
+}
+
+bool QGraphicsFrameCaptureMetal::isCapturing() const
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not query if capturing is in progress or not.";
+ return false;
+ }
+
+ return [m_captureManager isCapturing];
+}
+
+void QGraphicsFrameCaptureMetal::openCapture()
+{
+#if defined(Q_OS_MACOS)
+#if !QT_CONFIG(process)
+ qFatal("QGraphicsFrameCapture requires QProcess on macOS");
+#else
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not open XCode with a valid capture.";
+ return;
+ }
+
+ if (!m_process) {
+ m_process = new QProcess();
+ m_process->setProgram(QStringLiteral("xed"));
+ QStringList args;
+ args.append(QUrl::fromNSURL(m_traceURL).toLocalFile());
+ m_process->setArguments(args);
+ }
+
+ m_process->kill();
+ m_process->start();
+#endif
+#endif
+}
+
+void QGraphicsFrameCaptureMetal::updateCaptureFileName()
+{
+ m_traceURL = QUrl::fromLocalFile(m_capturePath + "/" + m_capturePrefix + "_" + QString::number(frameNumber) + ".gputrace").toNSURL();
+ // We need to remove the trace file if it already existed else MTLCaptureManager
+ // will fail to.
+ if ([NSFileManager.defaultManager fileExistsAtPath:m_traceURL.path])
+ [NSFileManager.defaultManager removeItemAtURL:m_traceURL error:nil];
+
+ m_captureDescriptor.outputURL = m_traceURL;
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qgraphicsframecapturemetal_p_p.h b/src/gui/util/qgraphicsframecapturemetal_p_p.h
new file mode 100644
index 0000000000..d703949de2
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapturemetal_p_p.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H
+#define QGRAPHICSFRAMEDECAPTUREMETAL_P_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 "qgraphicsframecapture_p_p.h"
+#include <QtCore/qmutex.h>
+#ifdef Q_OS_MACOS
+#include <QtCore/qprocess.h>
+#endif
+
+Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureManager);
+Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureDescriptor);
+Q_FORWARD_DECLARE_OBJC_CLASS(NSURL);
+
+QT_BEGIN_NAMESPACE
+
+class QGraphicsFrameCaptureMetal : public QGraphicsFrameCapturePrivate
+{
+public:
+ QGraphicsFrameCaptureMetal();
+ ~QGraphicsFrameCaptureMetal();
+
+ void setRhi(QRhi *rhi) override;
+ void startCaptureFrame() override;
+ void endCaptureFrame() override;
+ bool initialized() const override;
+ bool isCapturing() const override;
+ void openCapture() override;
+
+private:
+ void updateCaptureFileName();
+#if defined(Q_OS_MACOS) && QT_CONFIG(process)
+ QProcess *m_process = nullptr;
+#endif
+ MTLCaptureManager *m_captureManager = nullptr;
+ MTLCaptureDescriptor *m_captureDescriptor = nullptr;
+ NSURL *m_traceURL = nullptr;
+ bool m_initialized = false;
+ static uint frameNumber;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H
diff --git a/src/gui/util/qgraphicsframecapturerenderdoc.cpp b/src/gui/util/qgraphicsframecapturerenderdoc.cpp
new file mode 100644
index 0000000000..88ba9d839f
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapturerenderdoc.cpp
@@ -0,0 +1,310 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qgraphicsframecapturerenderdoc_p_p.h"
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qlibrary.h>
+#include <QtCore/qmutex.h>
+#include "QtGui/rhi/qrhi.h"
+#include "QtGui/rhi/qrhi_platform.h"
+#include "QtGui/qopenglcontext.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture")
+
+RENDERDOC_API_1_6_0 *QGraphicsFrameCaptureRenderDoc::s_rdocApi = nullptr;
+#if QT_CONFIG(thread)
+QBasicMutex QGraphicsFrameCaptureRenderDoc::s_frameCaptureMutex;
+#endif
+
+#if QT_CONFIG(opengl)
+static void *glNativeContext(QOpenGLContext *context) {
+ void *nctx = nullptr;
+ if (context != nullptr && context->isValid()) {
+#ifdef Q_OS_WIN
+ nctx = context->nativeInterface<QNativeInterface::QWGLContext>()->nativeContext();
+#endif
+
+#ifdef Q_OS_LINUX
+#if QT_CONFIG(egl)
+ QNativeInterface::QEGLContext *eglItf = context->nativeInterface<QNativeInterface::QEGLContext>();
+ if (eglItf)
+ nctx = eglItf->nativeContext();
+#endif
+
+#if QT_CONFIG(xcb_glx_plugin)
+ QNativeInterface::QGLXContext *glxItf = context->nativeInterface<QNativeInterface::QGLXContext>();
+ if (glxItf)
+ nctx = glxItf->nativeContext();
+#endif
+#endif
+
+#if QT_CONFIG(metal)
+ nctx = context->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext();
+#endif
+ }
+ return nctx;
+}
+#endif // QT_CONFIG(opengl)
+
+/*!
+ \class QGraphicsFrameCaptureRenderDoc
+ \internal
+ \brief The QGraphicsFrameCaptureRenderDoc class provides a way to capture a record of draw calls
+ for different graphics APIs.
+ \since 6.6
+ \inmodule QtGui
+
+ For applications that render using graphics APIs like Vulkan or OpenGL, it would be
+ convenient to have a way to check the draw calls done by the application. Specially
+ for applications that make a large amount of draw calls and the output is different
+ from what is expected.
+
+ This class acts as a wrapper over \l {https://renderdoc.org/}{RenderDoc} that allows
+ applications to capture a rendered frame either programmatically, by clicking a key
+ on the keyboard or both. The captured frame could be viewed later using RenderDoc GUI.
+
+ Read the \l {https://renderdoc.org/docs/index.html} {RenderDoc Documentation}
+ for more information.
+
+ \section1 API device handle
+
+ The functions that capture a frame like QGraphicsFrameCaptureRenderDoc::startCaptureFrame takes a device
+ pointer as argument. This pointer is unique for each graphics API and is associated
+ with the window that will be captured. This pointer has a default value of \c nullptr.
+ If no value is passed to the function the underlying API will try to find the device to
+ use, but it is not guaranteed specially in a multi-window applications.
+
+ For OpenGL, the pointer should be the OpenGL context on the platform OpenGL is being
+ used. For example, on Windows it should be \c HGLRC.
+
+ For Vulkan, the pointer should point to the dispatch table within the \c VkInstance.
+*/
+
+
+
+/*!
+ Creates a new object of this class. The constructor will load RenderDoc library
+ from the default path.
+
+ Only one instance of RenderDoc library is loaded at runtime which means creating
+ several instances of this class will not affect the RenderDoc initialization.
+*/
+
+QGraphicsFrameCaptureRenderDoc::QGraphicsFrameCaptureRenderDoc()
+ : m_nativeHandlesSet(false)
+{
+ if (!s_rdocApi)
+ init();
+}
+
+void QGraphicsFrameCaptureRenderDoc::setRhi(QRhi *rhi)
+{
+ if (!rhi)
+ return;
+
+ QRhi::Implementation backend = rhi->backend();
+ const QRhiNativeHandles *nh = rhi->nativeHandles();
+
+ switch (backend) {
+ case QRhi::Implementation::D3D11: {
+#ifdef Q_OS_WIN
+ const QRhiD3D11NativeHandles *d3d11nh = static_cast<const QRhiD3D11NativeHandles *>(nh);
+ m_nativeHandle = d3d11nh->dev;
+ break;
+#endif
+ qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D11. Check platform support";
+ break;
+ }
+ case QRhi::Implementation::D3D12: {
+#ifdef Q_OS_WIN
+ const QRhiD3D12NativeHandles *d3d12nh = static_cast<const QRhiD3D12NativeHandles *>(nh);
+ m_nativeHandle = d3d12nh->dev;
+ break;
+#endif
+ qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D12. Check platform support";
+ break;
+ }
+ case QRhi::Implementation::Vulkan: {
+#if QT_CONFIG(vulkan)
+ const QRhiVulkanNativeHandles *vknh = static_cast<const QRhiVulkanNativeHandles *>(nh);
+ m_nativeHandle = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(vknh->inst->vkInstance());
+ break;
+#endif
+ qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for Vulkan. Check platform support";
+ break;
+ }
+ case QRhi::Implementation::OpenGLES2: {
+#ifndef QT_NO_OPENGL
+ const QRhiGles2NativeHandles *glnh = static_cast<const QRhiGles2NativeHandles *>(nh);
+ m_nativeHandle = glNativeContext(glnh->context);
+ if (m_nativeHandle)
+ break;
+#endif
+ qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for OpenGL. Check platform support";
+ break;
+ }
+ case QRhi::Implementation::Metal:
+ case QRhi::Implementation::Null:
+ qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided."
+ " Metal and Null backends are not supported with RenderDoc";
+ break;
+ }
+
+ if (m_nativeHandle)
+ m_nativeHandlesSet = true;
+}
+
+/*!
+ Starts a frame capture using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi
+ \a device. This function must be called before QGraphicsFrameCaptureRenderDoc::endCaptureFrame.
+ \sa {API device handle}
+*/
+void QGraphicsFrameCaptureRenderDoc::startCaptureFrame()
+{
+
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
+ " Starting capturing can not be done.";
+ return;
+ }
+
+#if QT_CONFIG(thread)
+ // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
+ QMutexLocker locker(&s_frameCaptureMutex);
+#endif
+ if (s_rdocApi->IsFrameCapturing()) {
+ qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress, "
+ "will not initiate another one until"
+ " QGraphicsFrameCapture::endCaptureFrame is called.";
+ return;
+ }
+
+ qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to start.";
+ updateCapturePathAndTemplate();
+ s_rdocApi->StartFrameCapture(m_nativeHandle, nullptr);
+}
+
+/*!
+ Ends a frame capture started by a call to QGraphicsFrameCaptureRenderDoc::startCaptureFrame
+ using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi.
+ This function must be called after QGraphicsFrameCaptureRenderDoc::startCaptureFrame.
+ Otherwise, a warning message will be printend and nothing will happen.
+ \sa {API device handle}
+*/
+void QGraphicsFrameCaptureRenderDoc::endCaptureFrame()
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
+ " End capturing can not be done.";
+ return;
+ }
+
+#if QT_CONFIG(thread)
+ // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
+ QMutexLocker locker(&s_frameCaptureMutex);
+#endif
+ if (!s_rdocApi->IsFrameCapturing()) {
+ qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done"
+ " without a call to QGraphicsFrameCapture::startCaptureFrame";
+ return;
+ }
+
+ qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to end.";
+ uint32_t result = s_rdocApi->EndFrameCapture(m_nativeHandle, nullptr);
+
+ if (result) {
+ uint32_t count = s_rdocApi->GetNumCaptures();
+ uint32_t pathLength = 0;
+ s_rdocApi->GetCapture(count - 1, nullptr, &pathLength, nullptr);
+ if (pathLength > 0) {
+ QVarLengthArray<char> name(pathLength, 0);
+ s_rdocApi->GetCapture(count - 1, name.data(), &pathLength, nullptr);
+ m_capturedFilesNames.append(QString::fromUtf8(name.data(), -1));
+ }
+ }
+}
+
+void QGraphicsFrameCaptureRenderDoc::updateCapturePathAndTemplate()
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
+ " Updating save location can not be done.";
+ return;
+ }
+
+
+ QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix;
+ s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData());
+}
+
+/*!
+ Returns true if the API is loaded and can capture frames or not.
+*/
+bool QGraphicsFrameCaptureRenderDoc::initialized() const
+{
+ return s_rdocApi && m_nativeHandlesSet;
+}
+
+bool QGraphicsFrameCaptureRenderDoc::isCapturing() const
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
+ " Can not query if capturing is in progress or not.";
+ return false;
+ }
+
+ return s_rdocApi->IsFrameCapturing();
+}
+
+void QGraphicsFrameCaptureRenderDoc::openCapture()
+{
+ if (!initialized()) {
+ qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized."
+ " Can not open RenderDoc UI tool.";
+ return;
+ }
+
+#if QT_CONFIG(thread)
+ // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
+ QMutexLocker locker(&s_frameCaptureMutex);
+#endif
+ if (s_rdocApi->IsTargetControlConnected())
+ s_rdocApi->ShowReplayUI();
+ else
+ s_rdocApi->LaunchReplayUI(1, nullptr);
+}
+
+void QGraphicsFrameCaptureRenderDoc::init()
+{
+#if QT_CONFIG(thread)
+ // There is a single instance of RenderDoc library and it needs mutex for multithreading access.
+ QMutexLocker locker(&s_frameCaptureMutex);
+#endif
+
+ QLibrary renderDocLib(QStringLiteral("renderdoc"));
+ pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI) renderDocLib.resolve("RENDERDOC_GetAPI");
+ if (!renderDocLib.isLoaded() || (RENDERDOC_GetAPI == nullptr)) {
+ qCWarning(lcGraphicsFrameCapture) << renderDocLib.errorString().toLatin1();
+ return;
+ }
+
+ int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, static_cast<void **>(static_cast<void *>(&s_rdocApi)));
+
+ if (ret == 0) {
+ qCWarning(lcGraphicsFrameCapture) << "The requested RenderDoc API is invalid or not supported";
+ return;
+ }
+
+ s_rdocApi->MaskOverlayBits(RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None,
+ RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None);
+ s_rdocApi->SetCaptureKeys(nullptr, 0);
+ s_rdocApi->SetFocusToggleKeys(nullptr, 0);
+
+ QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix;
+ s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData());
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h
new file mode 100644
index 0000000000..c0d92ea733
--- /dev/null
+++ b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H
+#define QGRAPHICSFRAMECAPTURERENDERDOC_P_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 <renderdoc_app.h>
+#include "qgraphicsframecapture_p_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#if QT_CONFIG(thread)
+class QBasicMutex;
+#endif
+
+class QGraphicsFrameCaptureRenderDoc : public QGraphicsFrameCapturePrivate
+{
+public:
+ QGraphicsFrameCaptureRenderDoc();
+ ~QGraphicsFrameCaptureRenderDoc() = default;
+
+ void setRhi(QRhi *rhi) override;
+ void startCaptureFrame() override;
+ void endCaptureFrame() override;
+ bool initialized() const override;
+ bool isCapturing() const override;
+ void openCapture() override;
+
+private:
+ void init();
+ void updateCapturePathAndTemplate();
+ static RENDERDOC_API_1_5_0 *s_rdocApi;
+#if QT_CONFIG(thread)
+ static QBasicMutex s_frameCaptureMutex;
+#endif
+ bool m_nativeHandlesSet;
+};
+
+QT_END_NAMESPACE
+
+#endif // QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H
diff --git a/src/gui/util/qgridlayoutengine.cpp b/src/gui/util/qgridlayoutengine.cpp
index 9d64c87bde..07981e8388 100644
--- a/src/gui/util/qgridlayoutengine.cpp
+++ b/src/gui/util/qgridlayoutengine.cpp
@@ -13,6 +13,8 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+#define LAYOUTITEMSIZE_MAX (1 << 24)
+
template<typename T>
static void insertOrRemoveItems(QList<T> &items, int index, int delta)
{
@@ -194,7 +196,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz
sumAvailable = targetSize - totalBox.q_minimumSize;
if (sumAvailable > 0.0) {
- qreal sumDesired = totalBox.q_preferredSize - totalBox.q_minimumSize;
+ const qreal totalBox_preferredSize = qMin(totalBox.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX));
+ qreal sumDesired = totalBox_preferredSize - totalBox.q_minimumSize;
for (int i = 0; i < n; ++i) {
if (ignore.testBit(start + i)) {
@@ -203,7 +206,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz
}
const QGridLayoutBox &box = boxes.at(start + i);
- qreal desired = box.q_preferredSize - box.q_minimumSize;
+ const qreal box_preferredSize = qMin(box.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX));
+ qreal desired = box_preferredSize - box.q_minimumSize;
factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired);
sumFactors += factors[i];
}
@@ -716,7 +720,7 @@ void QGridLayoutItem::dump(int indent) const
if (q_alignment != 0)
qDebug("%*s Alignment: %x", indent, "", uint(q_alignment));
qDebug("%*s Horizontal size policy: %x Vertical size policy: %x",
- indent, "", sizePolicy(Qt::Horizontal), sizePolicy(Qt::Vertical));
+ indent, "", (unsigned int)sizePolicy(Qt::Horizontal), (unsigned int)sizePolicy(Qt::Vertical));
}
#endif
@@ -960,8 +964,11 @@ void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index)
for (int i = item->firstRow(); i <= item->lastRow(); ++i) {
for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) {
- if (itemAt(i, j))
- qWarning("QGridLayoutEngine::addItem: Cell (%d, %d) already taken", i, j);
+ const auto existingItem = itemAt(i, j);
+ if (existingItem) {
+ qWarning("QGridLayoutEngine::addItem: Can't add %s at cell (%d, %d) because it's already taken by %s",
+ qPrintable(item->toString()), i, j, qPrintable(existingItem->toString()));
+ }
setItemAt(i, j, item);
}
}
@@ -1175,7 +1182,7 @@ void QGridLayoutEngine::dump(int indent) const
{
qDebug("%*sEngine", indent, "");
- qDebug("%*s Items (%d)", indent, "", q_items.count());
+ qDebug("%*s Items (%lld)", indent, "", q_items.count());
int i;
for (i = 0; i < q_items.count(); ++i)
q_items.at(i)->dump(indent + 2);
diff --git a/src/gui/util/qgridlayoutengine_p.h b/src/gui/util/qgridlayoutengine_p.h
index e21d89dd2e..2f60cb99fd 100644
--- a/src/gui/util/qgridlayoutengine_p.h
+++ b/src/gui/util/qgridlayoutengine_p.h
@@ -24,6 +24,7 @@
#include <QtCore/qpair.h>
#include <QtCore/qsize.h>
#include <QtCore/qrect.h>
+#include <QtCore/qdebug.h>
#include <float.h>
#include "qlayoutpolicy_p.h"
@@ -283,6 +284,8 @@ public:
virtual QLayoutPolicy::ControlTypes controlTypes(LayoutSide side) const;
+ inline virtual QString toString() const { return QDebug::toString(this); }
+
QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent, Qt::Alignment align, bool snapToPixelGrid) const;
QGridLayoutBox box(Qt::Orientation orientation, bool snapToPixelGrid, qreal constraint = -1.0) const;
diff --git a/src/gui/util/qktxhandler.cpp b/src/gui/util/qktxhandler.cpp
index f04da929c3..35f1df1185 100644
--- a/src/gui/util/qktxhandler.cpp
+++ b/src/gui/util/qktxhandler.cpp
@@ -41,7 +41,7 @@ struct KTXHeader {
quint32 bytesOfKeyValueData;
};
-static const quint32 qktxh_headerSize = sizeof(KTXHeader);
+static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader);
// Currently unused, declared for future reference
struct KTXKeyValuePairItem {
@@ -71,11 +71,24 @@ struct KTXMipmapLevel {
*/
};
-// Returns the nearest multiple of 'rounding' greater than or equal to 'value'
-constexpr quint32 withPadding(quint32 value, quint32 rounding)
+// Returns the nearest multiple of 4 greater than or equal to 'value'
+static const std::optional<quint32> nearestMultipleOf4(quint32 value)
{
- Q_ASSERT(rounding > 1);
- return value + (rounding - 1) - ((value + (rounding - 1)) % rounding);
+ constexpr quint32 rounding = 4;
+ quint32 result = 0;
+ if (qAddOverflow(value, rounding - 1, &result))
+ return std::nullopt;
+ result &= ~(rounding - 1);
+ return result;
+}
+
+// Returns a view with prechecked bounds
+static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length)
+{
+ quint32 end = 0;
+ if (qAddOverflow(start, length, &end) || end > quint32(view.length()))
+ return {};
+ return view.sliced(start, length);
}
QKtxHandler::~QKtxHandler() = default;
@@ -83,8 +96,7 @@ QKtxHandler::~QKtxHandler() = default;
bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block)
{
Q_UNUSED(suffix);
-
- return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0);
+ return block.startsWith(ktxIdentifier);
}
QTextureFileData QKtxHandler::read()
@@ -93,55 +105,122 @@ QTextureFileData QKtxHandler::read()
return QTextureFileData();
const QByteArray buf = device()->readAll();
- const quint32 dataSize = quint32(buf.size());
- if (dataSize < qktxh_headerSize || !canRead(QByteArray(), buf)) {
- qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
+ if (static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) {
+ qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ if (!canRead(QByteArray(), buf)) {
+ qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData());
return QTextureFileData();
}
- const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.data());
- if (!checkHeader(*header)) {
- qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
+ if (buf.size() < qsizetype(qktxh_headerSize)) {
+ qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ KTXHeader header;
+ memcpy(&header, buf.data(), qktxh_headerSize);
+ if (!checkHeader(header)) {
+ qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData());
return QTextureFileData();
}
QTextureFileData texData;
texData.setData(buf);
- texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight)));
- texData.setGLFormat(decode(header->glFormat));
- texData.setGLInternalFormat(decode(header->glInternalFormat));
- texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat));
+ texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight)));
+ texData.setGLFormat(decode(header.glFormat));
+ texData.setGLInternalFormat(decode(header.glInternalFormat));
+ texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat));
- texData.setNumLevels(decode(header->numberOfMipmapLevels));
- texData.setNumFaces(decode(header->numberOfFaces));
+ texData.setNumLevels(decode(header.numberOfMipmapLevels));
+ texData.setNumFaces(decode(header.numberOfFaces));
+
+ const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData);
+ quint32 headerKeyValueSize;
+ if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) {
+ qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s",
+ logName().constData());
+ return QTextureFileData();
+ }
- const quint32 bytesOfKeyValueData = decode(header->bytesOfKeyValueData);
- if (qktxh_headerSize + bytesOfKeyValueData < quint64(buf.size())) // oob check
- texData.setKeyValueMetadata(decodeKeyValues(
- QByteArrayView(buf.data() + qktxh_headerSize, bytesOfKeyValueData)));
- quint32 offset = qktxh_headerSize + bytesOfKeyValueData;
+ if (headerKeyValueSize >= quint32(buf.size())) {
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ // File contains key/values
+ if (bytesOfKeyValueData > 0) {
+ auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData);
+ if (keyValueDataView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ auto keyValues = decodeKeyValues(keyValueDataView);
+ if (!keyValues) {
+ qWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s",
+ logName().constData());
+ return QTextureFileData();
+ }
+
+ texData.setKeyValueMetadata(*keyValues);
+ }
+
+ // Technically, any number of levels is allowed but if the value is bigger than
+ // what is possible in KTX V2 (and what makes sense) we return an error.
+ // maxLevels = log2(max(width, height, depth))
+ const int maxLevels = (sizeof(quint32) * 8)
+ - qCountLeadingZeroBits(std::max(
+ { header.pixelWidth, header.pixelHeight, header.pixelDepth }));
+
+ if (texData.numLevels() > maxLevels) {
+ qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
- constexpr int MAX_ITERATIONS = 32; // cap iterations in case of corrupt data
+ if (texData.numFaces() != 1 && texData.numFaces() != 6) {
+ qWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
- for (int level = 0; level < qMin(texData.numLevels(), MAX_ITERATIONS); level++) {
- if (offset + sizeof(quint32) > dataSize) // Corrupt file; avoid oob read
- break;
+ quint32 offset = headerKeyValueSize;
+ for (int level = 0; level < texData.numLevels(); level++) {
+ const auto imageSizeView = safeView(buf, offset, sizeof(quint32));
+ if (imageSizeView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
- const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.data() + offset));
- offset += sizeof(quint32);
+ const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data()));
+ offset += sizeof(quint32); // overflow checked indirectly above
- for (int face = 0; face < qMin(texData.numFaces(), MAX_ITERATIONS); face++) {
+ for (int face = 0; face < texData.numFaces(); face++) {
texData.setDataOffset(offset, level, face);
texData.setDataLength(imageSize, level, face);
// Add image data and padding to offset
- offset += withPadding(imageSize, 4);
+ const auto padded = nearestMultipleOf4(imageSize);
+ if (!padded) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ quint32 offsetNext;
+ if (qAddOverflow(offset, *padded, &offsetNext)) {
+ qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData());
+ return QTextureFileData();
+ }
+
+ offset = offsetNext;
}
}
if (!texData.isValid()) {
- qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData());
+ qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s",
+ logName().constData());
return QTextureFileData();
}
@@ -187,33 +266,83 @@ bool QKtxHandler::checkHeader(const KTXHeader &header)
return is2D && (isCubeMap || isCompressedImage);
}
-QMap<QByteArray, QByteArray> QKtxHandler::decodeKeyValues(QByteArrayView view) const
+std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const
{
QMap<QByteArray, QByteArray> output;
quint32 offset = 0;
- while (offset < view.size() + sizeof(quint32)) {
+ while (offset < quint32(view.size())) {
+ const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32));
+ if (keyAndValueByteSizeView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
+ return std::nullopt;
+ }
+
const quint32 keyAndValueByteSize =
- decode(qFromUnaligned<quint32>(view.constData() + offset));
- offset += sizeof(quint32);
+ decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data()));
- if (offset + keyAndValueByteSize > quint64(view.size()))
- break; // oob read
+ quint32 offsetKeyAndValueStart;
+ if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ quint32 offsetKeyAndValueEnd;
+ if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize);
+ if (keyValueView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
+ return std::nullopt;
+ }
// 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest.
// To separate the key and value we convert the complete data to utf-8 and find the first
// null terminator from the left, here we split the data into two.
- const auto str = QString::fromUtf8(view.constData() + offset, keyAndValueByteSize);
- const int idx = str.indexOf('\0'_L1);
- if (idx == -1)
- continue;
-
- const QByteArray key = str.left(idx).toUtf8();
- const size_t keySize = key.size() + 1; // Actual data size
- const QByteArray value = QByteArray::fromRawData(view.constData() + offset + keySize,
- keyAndValueByteSize - keySize);
-
- offset = withPadding(offset + keyAndValueByteSize, 4);
- output.insert(key, value);
+
+ const int idx = keyValueView.indexOf('\0');
+ if (idx == -1) {
+ qWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value");
+ return std::nullopt;
+ }
+
+ const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx);
+ if (keyView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ const quint32 keySize = idx + 1; // Actual data size
+
+ quint32 offsetValueStart;
+ if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ quint32 valueSize;
+ if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) {
+ qWarning(lcQtGuiTextureIO, "Underflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize);
+ if (valueView.isEmpty()) {
+ qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value");
+ return std::nullopt;
+ }
+
+ output.insert(keyView.toByteArray(), valueView.toByteArray());
+
+ const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd);
+ if (!offsetNext) {
+ qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value");
+ return std::nullopt;
+ }
+
+ offset = *offsetNext;
}
return output;
diff --git a/src/gui/util/qktxhandler_p.h b/src/gui/util/qktxhandler_p.h
index 7d54b20922..3a0b8fcf7e 100644
--- a/src/gui/util/qktxhandler_p.h
+++ b/src/gui/util/qktxhandler_p.h
@@ -17,6 +17,8 @@
#include "qtexturefilehandler_p.h"
+#include <optional>
+
QT_BEGIN_NAMESPACE
struct KTXHeader;
@@ -33,7 +35,7 @@ public:
private:
bool checkHeader(const KTXHeader &header);
- QMap<QByteArray, QByteArray> decodeKeyValues(QByteArrayView view) const;
+ std::optional<QMap<QByteArray, QByteArray>> decodeKeyValues(QByteArrayView view) const;
quint32 decode(quint32 val) const;
bool inverseEndian = false;
diff --git a/src/gui/util/qvalidator.cpp b/src/gui/util/qvalidator.cpp
index 8b0f9ac321..2a81006657 100644
--- a/src/gui/util/qvalidator.cpp
+++ b/src/gui/util/qvalidator.cpp
@@ -362,34 +362,45 @@ static qlonglong pow10(int exp)
return result;
}
-QValidator::State QIntValidator::validate(QString & input, int&) const
+template <typename T> static inline
+std::optional<QValidator::State> initialResultCheck(T min, T max, const ParsingResult &result)
{
- QByteArray buff;
- if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1,
- locale().numberOptions())) {
- return Invalid;
- }
+ if (result.state == ParsingResult::Invalid)
+ return QValidator::Invalid;
+ const CharBuff &buff = result.buff;
if (buff.isEmpty())
- return Intermediate;
+ return QValidator::Intermediate;
- const bool startsWithMinus(buff[0] == '-');
- if (b >= 0 && startsWithMinus)
- return Invalid;
+ char ch = buff[0];
+ const bool signConflicts = (min >= 0 && ch == '-') || (max < 0 && ch == '+');
+ if (signConflicts)
+ return QValidator::Invalid;
- const bool startsWithPlus(buff[0] == '+');
- if (t < 0 && startsWithPlus)
- return Invalid;
+ if (result.state == ParsingResult::Intermediate)
+ return QValidator::Intermediate;
- if (buff.size() == 1 && (startsWithPlus || startsWithMinus))
- return Intermediate;
+ return std::nullopt;
+}
- bool ok;
- qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok);
- if (!ok)
+QValidator::State QIntValidator::validate(QString & input, int&) const
+{
+ ParsingResult result =
+ locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1,
+ locale().numberOptions());
+
+ std::optional<State> opt = initialResultCheck(b, t, result);
+ if (opt)
+ return *opt;
+
+ const CharBuff &buff = result.buff;
+ QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10);
+ if (!r.ok())
return Invalid;
+ qint64 entered = r.result;
if (entered >= b && entered <= t) {
+ bool ok = false;
locale().toInt(input, &ok);
return ok ? Acceptable : Intermediate;
}
@@ -401,7 +412,7 @@ QValidator::State QIntValidator::validate(QString & input, int&) const
// of a number of digits equal to or less than the max value as intermediate.
int buffLength = buff.size();
- if (startsWithPlus)
+ if (buff[0] == '+')
buffLength--;
const int tLength = t != 0 ? static_cast<int>(std::log10(qAbs(t))) + 1 : 1;
@@ -414,15 +425,15 @@ QValidator::State QIntValidator::validate(QString & input, int&) const
/*! \reimp */
void QIntValidator::fixup(QString &input) const
{
- QByteArray buff;
- if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1,
- locale().numberOptions())) {
+ auto [parseState, buff] =
+ locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1,
+ locale().numberOptions());
+ if (parseState == ParsingResult::Invalid)
return;
- }
- bool ok;
- qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok);
- if (ok)
- input = locale().toString(entered);
+
+ QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10);
+ if (r.ok())
+ input = locale().toString(r.result);
}
/*!
@@ -646,19 +657,12 @@ QValidator::State QDoubleValidator::validate(QString & input, int &) const
QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QLocaleData::NumberMode numMode, const QLocale &locale) const
{
Q_Q(const QDoubleValidator);
- QByteArray buff;
- if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec, locale.numberOptions())) {
- return QValidator::Invalid;
- }
+ ParsingResult result =
+ locale.d->m_data->validateChars(input, numMode, q->dec, locale.numberOptions());
- if (buff.isEmpty())
- return QValidator::Intermediate;
-
- if (q->b >= 0 && buff.startsWith('-'))
- return QValidator::Invalid;
-
- if (q->t < 0 && buff.startsWith('+'))
- return QValidator::Invalid;
+ std::optional<QValidator::State> opt = initialResultCheck(q->b, q->t, result);
+ if (opt)
+ return *opt;
bool ok = false;
double i = locale.toDouble(input, &ok); // returns 0.0 if !ok
@@ -737,15 +741,16 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe
const QLocale &locale) const
{
Q_Q(const QDoubleValidator);
- QByteArray buff;
// Passing -1 as the number of decimals, because fixup() exists to improve
// an Intermediate value, if it can.
- if (!locale.d->m_data->validateChars(input, numMode, &buff, -1, locale.numberOptions()))
+ auto [parseState, buff] =
+ locale.d->m_data->validateChars(input, numMode, -1, locale.numberOptions());
+ if (parseState == ParsingResult::Invalid)
return;
- // buff now contains data in C locale.
+ // buff contains data in C locale.
bool ok = false;
- const double entered = buff.toDouble(&ok);
+ const double entered = QByteArrayView(buff).toDouble(&ok);
if (ok) {
// Here we need to adjust the output format accordingly
char mode;
@@ -769,7 +774,7 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe
if (eIndex < 0)
eIndex = buff.size();
precision = eIndex - (buff.contains('.') ? 1 : 0)
- - (buff.startsWith('-') || buff.startsWith('+') ? 1 : 0);
+ - (buff[0] == '-' || buff[0] == '+' ? 1 : 0);
}
// Use q->dec to limit the number of decimals, because we want the
// fixup() result to pass validate().
diff --git a/src/gui/vulkan/generated_header.txt b/src/gui/vulkan/licenseheader.h.in
index 3b6a0ea72a..3b6a0ea72a 100644
--- a/src/gui/vulkan/generated_header.txt
+++ b/src/gui/vulkan/licenseheader.h.in
diff --git a/src/gui/vulkan/qbasicvulkanplatforminstance.cpp b/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
index 31e27cdac5..bbe9f9e1cb 100644
--- a/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
+++ b/src/gui/vulkan/qbasicvulkanplatforminstance.cpp
@@ -259,8 +259,13 @@ void QBasicPlatformVulkanInstance::initInstance(QVulkanInstance *instance, const
VkInstanceCreateInfo instInfo = {};
instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instInfo.pApplicationInfo = &appInfo;
- if (!flags.testFlag(QVulkanInstance::NoPortabilityDrivers))
- instInfo.flags |= 0x00000001; // VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR
+ if (!flags.testFlag(QVulkanInstance::NoPortabilityDrivers)) {
+ // With old Vulkan SDKs setting a non-zero flags gives a validation error.
+ // Whereas from 1.3.216 on the portability bit is required for MoltenVK to function.
+ // Hence the version check.
+ if (m_supportedApiVersion >= QVersionNumber(1, 3, 216))
+ instInfo.flags |= 0x00000001; // VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR
+ }
QList<const char *> layerNameVec;
for (const QByteArray &ba : std::as_const(m_enabledLayers))
diff --git a/src/gui/vulkan/qvulkanfunctions.cpp b/src/gui/vulkan/qvulkanfunctions.cpp
index e6bf10068f..6a30799502 100644
--- a/src/gui/vulkan/qvulkanfunctions.cpp
+++ b/src/gui/vulkan/qvulkanfunctions.cpp
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
\wrapper
\brief The QVulkanFunctions class provides cross-platform access to the
- instance level core Vulkan 1.2 API.
+ instance level core Vulkan 1.3 API.
Qt and Qt applications do not link to any Vulkan libraries by default.
Instead, all functions are resolved dynamically at run time. Each
@@ -45,17 +45,17 @@ QT_BEGIN_NAMESPACE
\l{https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkGetInstanceProcAddr.html}{the
man page for vkGetInstanceProcAddr} for more information.
- \note The member function prototypes for Vulkan 1.1 and 1.2 commands are
- ifdefed with the appropriate \c{VK_VERSION_1_x} that is defined by the
- Vulkan headers. Therefore these functions will only be callable by an
+ \note The member function prototypes for Vulkan 1.1, 1.2, and 1.3 commands
+ are \c ifdefed with the appropriate \c{VK_VERSION_1_x} that is defined by
+ the Vulkan headers. As such, these functions will only be callable by an
application when the system's (on which the application is built) Vulkan
- header is new enough and it contains 1.1 and 1.2 Vulkan API definitions.
- When building Qt from source, this has an additional consequence: the
- Vulkan headers on the build environment must also be 1.1 and 1.2 capable in
- order to get a Qt build that supports resolving the 1.1 and 1.2 API
- commands. If either of these conditions is not met, applications will only
- be able to call the Vulkan 1.0 commands through QVulkanFunctions and
- QVulkanDeviceFunctions.
+ header is new enough and it contains 1.1, 1.2, or 1.3 Vulkan API
+ definitions. When building Qt from source, this has an additional
+ consequence: the Vulkan headers on the build environment must also be 1.1,
+ 1.2, and 1.3 compatible to get a Qt build that supports resolving
+ the 1.1, 1.2, and 1.3 API commands. If neither of these conditions is met,
+ applications will only be able to call the Vulkan 1.0 commands through
+ QVulkanFunctions and QVulkanDeviceFunctions.
\sa QVulkanInstance, QVulkanDeviceFunctions, QWindow::setVulkanInstance(), QWindow::setSurfaceType()
*/
@@ -68,7 +68,7 @@ QT_BEGIN_NAMESPACE
\wrapper
\brief The QVulkanDeviceFunctions class provides cross-platform access to
- the device level core Vulkan 1.2 API.
+ the device level core Vulkan 1.3 API.
Qt and Qt applications do not link to any Vulkan libraries by default.
Instead, all functions are resolved dynamically at run time. Each
diff --git a/src/gui/vulkan/qvulkaninstance.cpp b/src/gui/vulkan/qvulkaninstance.cpp
index fc157bdae1..6d3020d62d 100644
--- a/src/gui/vulkan/qvulkaninstance.cpp
+++ b/src/gui/vulkan/qvulkaninstance.cpp
@@ -245,7 +245,8 @@ QVulkanInstance::QVulkanInstance()
/*!
Destructor.
- \note current() will return \nullptr once the instance is destroyed.
+ \note \l {QVulkanInstance::}{vkInstance()} will return \nullptr once the
+ instance is destroyed.
*/
QVulkanInstance::~QVulkanInstance()
{
@@ -540,8 +541,8 @@ void QVulkanInstance::setApiVersion(const QVersionNumber &vulkanVersion)
\return true if successful, false on error or when Vulkan is not supported.
- When successful, the pointer to this QVulkanInstance is retrievable via the
- static function current().
+ When successful, the pointer to this QVulkanInstance is retrievable via
+ \l {QVulkanInstance::}{vkInstance()}.
The Vulkan instance and library is available as long as this
QVulkanInstance exists, or until destroy() is called.
diff --git a/src/gui/vulkan/qvulkanwindow.cpp b/src/gui/vulkan/qvulkanwindow.cpp
index 5a86b43290..00a5c5f869 100644
--- a/src/gui/vulkan/qvulkanwindow.cpp
+++ b/src/gui/vulkan/qvulkanwindow.cpp
@@ -178,12 +178,11 @@ Q_DECLARE_LOGGING_CATEGORY(lcGuiVk)
As an exception to this rule, \c robustBufferAccess is never enabled. Use the
callback mechanism described below, if enabling that feature is desired.
- Just enabling the 1.0 core features is not always sufficient, and therefore
- full control over the VkPhysicalDeviceFeatures used for device creation is
- possible too by registering a callback function with
+ This is not always desirable, and may be insufficient with Vulkan 1.1 and
+ higher. Therefore, full control over the VkPhysicalDeviceFeatures used for
+ device creation is possible too by registering a callback function with
setEnabledFeaturesModifier(). When set, the callback function is invoked,
- letting it alter the VkPhysicalDeviceFeatures, instead of enabling only the
- 1.0 core features.
+ letting it alter the VkPhysicalDeviceFeatures or VkPhysicalDeviceFeatures2.
\sa QVulkanInstance, QWindow
*/
@@ -448,7 +447,7 @@ void QVulkanWindow::setPreferredColorFormats(const QList<VkFormat> &formats)
static struct {
VkSampleCountFlagBits mask;
int count;
-} qvk_sampleCounts[] = {
+} q_vk_sampleCounts[] = {
// keep this sorted by 'count'
{ VK_SAMPLE_COUNT_1_BIT, 1 },
{ VK_SAMPLE_COUNT_2_BIT, 2 },
@@ -488,7 +487,7 @@ QList<int> QVulkanWindow::supportedSampleCounts()
VkSampleCountFlags depth = limits->framebufferDepthSampleCounts;
VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts;
- for (const auto &qvk_sampleCount : qvk_sampleCounts) {
+ for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
if ((color & qvk_sampleCount.mask)
&& (depth & qvk_sampleCount.mask)
&& (stencil & qvk_sampleCount.mask))
@@ -537,7 +536,7 @@ void QVulkanWindow::setSampleCount(int sampleCount)
return;
}
- for (const auto &qvk_sampleCount : qvk_sampleCounts) {
+ for (const auto &qvk_sampleCount : q_vk_sampleCounts) {
if (qvk_sampleCount.count == sampleCount) {
d->sampleCount = qvk_sampleCount.mask;
return;
@@ -702,17 +701,22 @@ void QVulkanWindowPrivate::init()
devInfo.enabledExtensionCount = devExts.size();
devInfo.ppEnabledExtensionNames = devExts.constData();
- VkPhysicalDeviceFeatures features;
- memset(&features, 0, sizeof(features));
- if (enabledFeaturesModifier) {
+ VkPhysicalDeviceFeatures features = {};
+ VkPhysicalDeviceFeatures2 features2 = {};
+ if (enabledFeatures2Modifier) {
+ features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+ enabledFeatures2Modifier(features2);
+ devInfo.pNext = &features2;
+ } else if (enabledFeaturesModifier) {
enabledFeaturesModifier(features);
+ devInfo.pEnabledFeatures = &features;
} else {
// Enable all supported 1.0 core features, except ones that likely
// involve a performance penalty.
f->vkGetPhysicalDeviceFeatures(physDev, &features);
features.robustBufferAccess = VK_FALSE;
+ devInfo.pEnabledFeatures = &features;
}
- devInfo.pEnabledFeatures = &features;
// Device layers are not supported by QVulkanWindow since that's an already deprecated
// API. However, have a workaround for systems with older API and layers (f.ex. L4T
@@ -1625,16 +1629,12 @@ void QVulkanWindow::setQueueCreateInfoModifier(const QueueCreateInfoModifier &mo
praticular, \c robustBufferAccess is always disabled in order to avoid
unexpected performance hits.
- This however is not always sufficient when working with Vulkan 1.1 or 1.2
- features and extensions. Hence this callback mechanism.
-
The VkPhysicalDeviceFeatures reference passed in is all zeroed out at the
point when the function is invoked. It is up to the function to change
- members to true, or set up \c pNext chains as it sees fit.
+ members as it sees fit.
- \note When setting up \c pNext chains, make sure the referenced objects
- have a long enough lifetime, for example by storing them as member
- variables in the QVulkanWindow subclass.
+ \note To control Vulkan 1.1, 1.2, or 1.3 features, use
+ EnabledFeatures2Modifier instead.
\sa setEnabledFeaturesModifier()
*/
@@ -1642,9 +1642,14 @@ void QVulkanWindow::setQueueCreateInfoModifier(const QueueCreateInfoModifier &mo
/*!
Sets the enabled device features modification function \a modifier.
- \sa EnabledFeaturesModifier
+ \note To control Vulkan 1.1, 1.2, or 1.3 features, use
+ the overload taking a EnabledFeatures2Modifier instead.
+
+ \note \a modifier is passed to the callback function with all members set
+ to false. It is up to the function to change members as it sees fit.
- \since 6.4
+ \since 6.7
+ \sa EnabledFeaturesModifier
*/
void QVulkanWindow::setEnabledFeaturesModifier(const EnabledFeaturesModifier &modifier)
{
@@ -1653,6 +1658,45 @@ void QVulkanWindow::setEnabledFeaturesModifier(const EnabledFeaturesModifier &mo
}
/*!
+ \typedef QVulkanWindow::EnabledFeatures2Modifier
+
+ A function that is called during graphics initialization to alter the
+ VkPhysicalDeviceFeatures2 that is changed to the VkDeviceCreateInfo.
+
+ By default QVulkanWindow enables all Vulkan 1.0 core features that the
+ physical device reports as supported, with certain exceptions. In
+ praticular, \c robustBufferAccess is always disabled in order to avoid
+ unexpected performance hits.
+
+ This however is not always sufficient when working with Vulkan 1.1, 1.2, or
+ 1.3 features and extensions. Hence this callback mechanism. If only Vulkan
+ 1.0 is relevant at run time, use setEnabledFeaturesModifier() instead.
+
+ The VkPhysicalDeviceFeatures2 reference passed to the callback function
+ with \c sType set, but the rest zeroed out. It is up to the function to
+ change members to true, or set up \c pNext chains as it sees fit.
+
+ \note When setting up \c pNext chains, make sure the referenced objects
+ have a long enough lifetime, for example by storing them as member
+ variables in the QVulkanWindow subclass.
+
+ \since 6.7
+ \sa setEnabledFeaturesModifier()
+ */
+
+/*!
+ Sets the enabled device features modification function \a modifier.
+ \overload
+ \since 6.7
+ \sa EnabledFeatures2Modifier
+*/
+void QVulkanWindow::setEnabledFeaturesModifier(EnabledFeatures2Modifier modifier)
+{
+ Q_D(QVulkanWindow);
+ d->enabledFeatures2Modifier = std::move(modifier);
+}
+
+/*!
Returns true if this window has successfully initialized all Vulkan
resources, including the swapchain.
@@ -1852,13 +1896,26 @@ void QVulkanWindowRenderer::logicalDeviceLost()
{
}
+QSize QVulkanWindowPrivate::surfacePixelSize() const
+{
+ Q_Q(const QVulkanWindow);
+ VkSurfaceCapabilitiesKHR surfaceCaps = {};
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDevs.at(physDevIndex), surface, &surfaceCaps);
+ VkExtent2D bufferSize = surfaceCaps.currentExtent;
+ if (bufferSize.width == uint32_t(-1)) {
+ Q_ASSERT(bufferSize.height == uint32_t(-1));
+ return q->size() * q->devicePixelRatio();
+ }
+ return QSize(int(bufferSize.width), int(bufferSize.height));
+}
+
void QVulkanWindowPrivate::beginFrame()
{
if (!swapChain || framePending)
return;
Q_Q(QVulkanWindow);
- if (q->size() * q->devicePixelRatio() != swapChainImageSize) {
+ if (swapChainImageSize != surfacePixelSize()) {
recreateSwapChain();
if (!swapChain)
return;
@@ -1927,7 +1984,7 @@ void QVulkanWindowPrivate::beginFrame()
}
if (frameGrabbing)
- frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888);
+ frameGrabTargetImage = QImage(swapChainImageSize, QImage::Format_RGBA8888); // the format is as documented
if (renderer) {
framePending = true;
@@ -2443,6 +2500,19 @@ VkFormat QVulkanWindow::depthStencilFormat() const
This usually matches the size of the window, but may also differ in case
\c vkGetPhysicalDeviceSurfaceCapabilitiesKHR reports a fixed size.
+ In addition, it has been observed on some platforms that the
+ Vulkan-reported surface size is different with high DPI scaling active,
+ meaning the QWindow-reported
+ \l{QWindow::}{size()} multiplied with the \l{QWindow::}{devicePixelRatio()}
+ was 1 pixel less or more when compared to the value returned from here,
+ presumably due to differences in rounding. Rendering code should be aware
+ of this, and any related rendering logic must be based in the value returned
+ from here, never on the QWindow-reported size. Regardless of which pixel size
+ is correct in theory, Vulkan rendering must only ever rely on the Vulkan
+ API-reported surface size. Otherwise validation errors may occur, e.g. when
+ setting the viewport, because the application-provided values may become
+ out-of-bounds from Vulkan's perspective.
+
\note Calling this function is only valid from the invocation of
QVulkanWindowRenderer::initSwapChainResources() up until
QVulkanWindowRenderer::releaseSwapChainResources().
@@ -2714,6 +2784,12 @@ bool QVulkanWindow::supportsGrab() const
incomplete image, that has the correct size but not the content yet. The
content will be delivered via the frameGrabbed() signal in the latter case.
+ The returned QImage always has a format of QImage::Format_RGBA8888. If the
+ colorFormat() is \c VK_FORMAT_B8G8R8A8_UNORM, the red and blue channels are
+ swapped automatically since this format is commonly used as the default
+ choice for swapchain color buffers. With any other color buffer format,
+ there is no conversion performed by this function.
+
\note This function should not be called when a frame is in progress
(that is, frameReady() has not yet been called back by the application).
@@ -2742,6 +2818,9 @@ QImage QVulkanWindow::grab()
d->frameGrabbing = true;
d->beginFrame();
+ if (d->colorFormat == VK_FORMAT_B8G8R8A8_UNORM)
+ d->frameGrabTargetImage = std::move(d->frameGrabTargetImage).rgbSwapped();
+
return d->frameGrabTargetImage;
}
diff --git a/src/gui/vulkan/qvulkanwindow.h b/src/gui/vulkan/qvulkanwindow.h
index d0bdd3683e..537dbc4ae1 100644
--- a/src/gui/vulkan/qvulkanwindow.h
+++ b/src/gui/vulkan/qvulkanwindow.h
@@ -54,6 +54,14 @@ public:
virtual void logicalDeviceLost();
};
+#ifndef VK_VERSION_1_1
+typedef struct VkPhysicalDeviceFeatures2 {
+ VkStructureType sType;
+ void* pNext;
+ VkPhysicalDeviceFeatures features;
+} VkPhysicalDeviceFeatures2;
+#endif
+
class Q_GUI_EXPORT QVulkanWindow : public QWindow
{
Q_OBJECT
@@ -79,6 +87,8 @@ public:
typedef std::function<void(VkPhysicalDeviceFeatures &)> EnabledFeaturesModifier;
void setEnabledFeaturesModifier(const EnabledFeaturesModifier &modifier);
+ typedef std::function<void(VkPhysicalDeviceFeatures2 &)> EnabledFeatures2Modifier;
+ void setEnabledFeaturesModifier(EnabledFeatures2Modifier modifier);
void setPreferredColorFormats(const QList<VkFormat> &formats);
diff --git a/src/gui/vulkan/qvulkanwindow_p.h b/src/gui/vulkan/qvulkanwindow_p.h
index 9eaead9e07..7a0ee091e6 100644
--- a/src/gui/vulkan/qvulkanwindow_p.h
+++ b/src/gui/vulkan/qvulkanwindow_p.h
@@ -36,6 +36,7 @@ public:
void init();
void reset();
bool createDefaultRenderPass();
+ QSize surfacePixelSize() const;
void recreateSwapChain();
uint32_t chooseTransientImageMemType(VkImage img, uint32_t startIndex);
bool createTransientImage(VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspectMask,
@@ -68,6 +69,7 @@ public:
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
QVulkanWindow::QueueCreateInfoModifier queueCreateInfoModifier;
QVulkanWindow::EnabledFeaturesModifier enabledFeaturesModifier;
+ QVulkanWindow::EnabledFeatures2Modifier enabledFeatures2Modifier;
VkDevice dev = VK_NULL_HANDLE;
QVulkanDeviceFunctions *devFuncs;